2009-04-17 106 views
1

我问了一个similar question yesterday,这是一种技术特有的,但现在我发现自己在广泛意义上对这个主题感到疑惑。基于RTTI或基类修改的基础或子类法案

为了简便起见,我们有两个类,A和B,其中B是由A,B衍生真正的“是” A,和所有A中所定义的例程具有相同的含义B.

假设我们想要显示一个As列表,其中一些实际上是Bs。当我们遍历As的列表时,如果当前对象实际上是一个B,我们想要显示一些Bs附加属性....或者我们只是想要区分Bs的颜色,但A和B都没有任何概念“颜色”或“显示内容”。

解决方案:

  1. 使A类由基本上包括一个称为ISB中的方法,()返回假半知道B的。 B将重写该方法并返回true。显示代码将具有如下检查:if(currentA.isB())B b = currentA;

  2. 在A中提供一个显示()方法,B可以覆盖....但是然后我们开始合并UI和模型。我不会考虑这个,除非有一些很酷的技巧我没有看到。

  3. 使用instanceof来检查,如果要显示的对象的当前确实是一个B.

  4. 距离b。添加所有的垃圾,以A,即使它并不适用于A.基本上只包含A中的B(不从A继承)并将其设置为null,直到它适用。这有点吸引人。这与#1相似,我猜是有继承的构成。

看起来这个问题似乎应该不时出现,并有一个明显的解决方案。

所以我想这个问题,也许真的可以归结为:

如果我有一个由延伸的基类,添加新的功能(不只是改变了基类的现有行为)的子类,是我在做一些悲惨的事情?当我们尝试对可能是A或B的对象集合进行操作时,它似乎立即崩溃。

回答

2

选项2(或1和2的混合)的变体可能是有意义的:毕竟,多态性是“Bs是As但需要在情形X中表现不同的标准解决方案”。同意,display()方法可能会将模型绑定到UI过于密切,但可能在UI级别所需的不同渲染反映了模型级别的语义或行为差异。那些可以用方法捕获吗?例如,它可以是一个getPriority()(例如)方法,而不是直接使用getDisplayColour()方法,A和B返回不同的值,但它仍然由UI决定如何将其转换为颜色?

鉴于你更普遍的问题,不过,“我们怎么能处理额外行为,我们不能或不会允许通过基类的多态访问,”例如,如果基类ISN”在我们的控制下,你的选择可能是选项3,访问者模式或助手类。在这两种情况下,您都有效地将多态性扩展到外部实体 - 在选项3中,UI(例如,演示者或控制器)执行instanceOf检查,并根据它是否为B来执行不同的事情;在访客或助手的情况下,新的班级。以您的示例为例,访问者可能是过度杀伤性的(另外,如果您不能/不愿意更改基类以适应它,就不可能实现它),所以我建议一个简单的类像“渲染”:

public abstract class Renderer { 
    public static Renderer Create(A obj) { 
    if (obj instanceOf B) 
     return new BRenderer(); 
    else 
     return new ARenderer(); 
    } 

    public abstract Color getColor(); 
} 

// implementations of ARenderer and BRenderer per your UI logic 

此封装运行时类型检查和高达捆绑代码到合理地定义的类与明确的职责,而无需观众的概念开销。 (但是,根据GrizzlyNyo的回答,如果你的层次结构或功能集比你在这里展示的要复杂得多,访问者可能会更合适,但很多人发现访问者很难找到他们的头,我倾向于避免它简单的情况 - 但你的里程可能会有所不同。)

+0

第二段的+1。这是需要区分的外部代码,因为两个类不关心。一个班级责任,而您的班级(层级)责任不是选择在窗口中使用的颜色。 – 2009-04-18 09:26:25

0

这看起来像Visitor设计模式(也称为“Double Dispatch” )。

请参阅this answer以获取关于访问者和复合模式的详细解释的链接。

1

itowlson给出的答案涵盖了问题的绝大部分。我现在将尽我所能处理最后一段。

继承应该实现重用,因为您的派生类可以在旧代码中重用,而不是重用基类的某些部分(可以使用聚合)。从这个观点来看,如果你有一个类在新的代码上使用一些新的功能,但应该作为一个前类透明地使用,那么继承就是你的解决方案。新代码可以使用新功能,旧代码将无缝地使用您的新对象。

虽然这是一般意图,但还是有一些常见的瑕疵,这里的线条很微妙,您的问题恰恰就是这条线。如果你有一个base类型的对象集合,那应该是因为这些对象只能用于base的方法。他们是'基地',表现得像基地。

在C++中使用'instanceof'或downcasts(dynamic_cast <>())来检测真正的运行时类型是我在代码审查中标记的东西,并且只有在程序员详细解释了为什么后才接受其他选项比该解决方案更糟糕。我会接受它,例如,在itowlson的回答下,前提是信息不适用于基于给定的操作。也就是说,基本类型没有任何方法可以为调用者提供足够的信息来确定颜色。如果包含这样的操作是没有意义的:除了prepresentation颜色,你是否会根据相同的信息对对象执行任何操作?如果逻辑取决于实际类型,那么操作应该在基类中被派生类中的重写。如果这是不可能的(操作是新的,并且只对于某些给定的子类型),那么至少应该在基础中进行操作,以允许调用者确定downcast不会失败。然后再次,我真的需要一个合理的理由让调用者代码需要知道真实类型。为什么用户想用不同的颜色来看它?用户会对每种类型执行不同的操作吗?

如果最终需要使用代码绕过类型系统,那么您的设计对它有一个奇怪的smell。当然,永远不要说永不言败,但你可以肯定地说:避免根据instance of或逻辑downcasts。

+0

在非常简单的情况下,你所说的(“新功能使用新对象,开心!”)是真实的。现在考虑这个非常真实的场景:您实现了继承旧基类“A”的新子类“X”。您将一个混合`X`和`A`的容器送入``OldMethod()`,它不能在`X`上操作,但是您希望将结果送入`NewMethod()`,它识别并使用'X `和`A`。 `NewMethod()`需要以某种方式区分`X`和`A`。 – kizzx2 2010-08-26 14:54:33