使抽象类不可见;或者:隐藏我的香蕉人

本文关键字:隐藏 我的 香蕉 或者 抽象类 | 更新日期: 2023-09-27 18:28:37

背景:我还是一个C#新手,这是我的第一个继承大项目。下面的故事是我目前处境的一个简化例子:

假设我有一个名为LivingOrganism的类。每个生物都可以使用这个类作为基础,并继承所有生物共同的功能。

当我研究其中一些derived类时,我发现香蕉和人类非常相似。尽管这没有多大意义,而且它们看起来一点也不相似,但它们显然有大部分共同的"功能"。

代码中的重复很糟糕,所以我写了一个新类来降低维护成本。这个类被称为:BananaHuman。我的Human类和Banana类继承自BananaHuman


问题:

我对我的BananaHuman没有问题(即我理解它的含义以及为什么存在)。但最终,其他人(甚至是那些不(完全)了解继承的人)将不得不使用我的LivingCreatures.dll。他们也不会理解为什么intellisense在键入"B"时建议BananaHuman。

并考虑以下代码:

//Valid and makes sense.
foreach(LivingOrganism foo in cityOfNeyYork) { /*embedded statement*/ }

但是想象一下,如果我们用BananaHuman代替Living Organism,它会看起来多么奇怪/令人困惑。

我不能将BananaHuman设为privateprotectedprotected internal(命名空间中定义的元素不能以这种方式显式声明)。我也不能使它成为internal,因为HumanBananapublic。如果我尝试这样做,我会收到一条错误消息,说存在inconsistent accessibility问题。

我觉得我错过了显而易见的东西,但我该怎么办?我只剩下几个选项/问题:

  1. 是否可以"隐藏"BananaHuman以避免混淆
  2. 我是否应该将BananaHuman重写为非常长的技术性内容,如DnaRelatedOrganismsType[X],其中"X"描述它们之间的独特关系
  3. 我应该删除BananaHuman,让HumanBanana继承LivingOrganism,并在需要更改时进行额外的维护吗
  4. 还有其他我完全没有的解决方案吗

我四处搜索,找不到这种情况的"固定模式"。我发现这个问题的标题相似,但我不知道答案是否适用,因为他似乎在问完全不同的问题。

非常感谢您的帮助!

使抽象类不可见;或者:隐藏我的香蕉人

您可以使用EditorBrowsableAttribute并将其应用于您的类。如果有人在使用你的.dll,这将使你的类从Intellisense中消失。如果你引用了你的项目而不是dll,它仍然可见。

使用方式:

[EditorBrowsable(EditorBrowsableState.Never)]
public class BananaHuman
{
    //....
}

所以,如果你把你的.dll给我,我不会在Intellisense中看到BananaHuman弹出。但如果我检查Banana或Human类,我仍然会看到它继承自BananaHuman,因为事实就是这样。EditorBrowsable属性只是让它从Intellisense中消失,这就是你想要的。

现有的答案是对intellisense隐藏BananaHuman的特定问题的一个很好的技术解决方案。但是,由于OP也询问了更改设计的问题,我认为快速回答为什么BananaHuman的存在是一种代码气味,它可能是重构的候选者也在问题的范围内。


您可能听说过SOLID,它是五个重要设计原则的缩写。BananaHuman与其中两个原则背道而驰:单一责任原则(SRP)和开放/封闭原则

香蕉和人类可能共享很多DNA,但就像代码一样,它们也应该进化,并且可能彼此独立进化。同样的DNA可能并不总是完全共享的。SRP指出,一个类只应该有一个责任,或者——模棱两可地——应该只有一个改变的理由。但是BananaHuman总是自动地有至少两个可能的改变原因——Banana的规格改变或Human的规格改变。

为什么BananaHuman会出现这种情况,而不是所有通用基类都会出现这种情形?因为基类应该表示一个定义良好的概念,就像其他任何类一样。因此,例如Mammal只有在构成哺乳动物概念的特征发生变化的情况下才会发生变化。如果一种特定的哺乳动物进化到脱发,那么会改变的是该动物的类别,而不是Mammal的基本类别。另一方面,BananaHuman根据定义是"香蕉和人类共同的特征",因此它总是与至少两个而不是一个概念耦合。同样,香蕉和人之间可能有几个共同点,它们之间没有太多其他关系。把这些都推到一个类中会降低凝聚力,并把更多的责任堆积在一个地方。

OCP规定,软件实体(如接口、类或方法)应向扩展开放,但在添加或更改需求时应向修改开放。例如,如果添加了另一个与BananaHuman具有相同特征的活体,则必须更改名称。或者,如果它只共享一些特性,那么您将不得不在基类之间来回移动,如果多次出现这种情况,甚至可能会遇到多个继承问题。我相信还有很多其他情况也会导致OCP违规。


那么你应该做什么

好吧,如果你读了上面的文章,认为BananaHuman的表征是不公平的,而且实际上它确实映射到了一个定义很好的概念,就像Mammal一样,那么。。。将其重命名为实际名称!这就是你所需要做的,你可能很适合去。名字长不长也没关系(尽管理想情况下简洁更好,而且你应该确保长度不会表明你把多个东西拼凑成一系列单词)。

如果这不是答案,那么就研究组合而非继承的思想。例如,如果有多个都有肺的生物体,则不创建LivingOrganismWithLungs类,而是创建Lungs类,并使每个有肺的活体都包含一个实例。如果你能像这样把常见的特性分离成它们自己的类,那么你就有了一个更好的解决方案。

如果这两者都不可能(罕见,但可能发生),那么BananaHuman可能是剩下的最佳选择。与"不要重复自己"(DRY)违规行为相比,评估SRP和OCP问题必须取决于您的判断。