问题 接口作为功能或接口作为类型


假设我有这样的要求:
系统中的对象都派生自一个名为IObject的基类,它可能包含带颜色的对象,带有转换的对象,以及两者。

现在有两种方法来设计类层次结构。
第一个是:

只是让具体类派生自   IObject,还选择“功能”   接口作为其基类   表明它支持这种行为,   喜欢界面:IHasColor,
  IHasTransformation

第二个是:

组织基类,然后让   从一个派生出的具体类   他们:IObject,IColorObject,   ITransfromationObject,   IColorAndTransformationObject

我更喜欢第一个(它有一个正式的名字吗?),因为它更灵活,你可以看到第二个可能有类组合爆炸问题,当有许多属性,如颜色,转换......

我想知道你的想法和建议。

谢谢。


5505
2018-06-22 06:46


起源

为什么以前的答案之一被删除??? - Baiyan Huang
这个问题是 不是语言不可知论者 如果是的话,你可以考虑更多优雅的替代方案,比如使用mixins,根据你描述的语言的性质(使用接口,我假设C#或java?)简直是不可能的 - Pablo Fernandez
@Pablo Fernandez mixin是我认为第一个解决方案可能的名称 - Baiyan Huang
不要这么认为。但如果它真的与语言无关,那很好我将在今天晚些时候创建一个mixin解决方案。如果你正在使用java,你也可以在JVM上使用它(通过scala):) - Pablo Fernandez


答案:


类抽象的真实概念 对象类型

接口抽象的真实概念 对象的行为或能力

那么问题变成了,“颜色”是对象的属性还是对象的能力?

设计层次结构时,您将世界限制在更狭窄的空间中。如果将颜色作为对象的属性,那么您将拥有一些对象,即具有颜色的对象和不具有颜色的对象。这适合你的“世界”吗?

如果你把它建模为一个功能(接口),那么你将拥有能够为世界提供颜色的对象,比如演员。

对于转换,同样的逻辑适用。你可以将世界分成两种对象,即可以变换的对象和不能变换的对象,或者你可以将其视为一种能力,一个对象可能有能力将自己转化为另一种东西。

对我来说,从这个角度来看,有意义的是:

  • 颜色是对象的属性。实际上每个物体都应该有一种颜色,即使它是透明的,即使它是反射的,即使它是“无”(祝你好运找出一个有颜色的物体=无物在现实世界中的意思,仍然可能在你的程序逻辑)。
  • 转换是一种能力,即和接口,是对象 能够做到除其他外,该对象可能会或可能不会这样做。

8
2018-06-29 09:14



正是我在写答案时的想法,但你用的是更少更好的词:) - grapkulec
非常雄辩地说! - Mrchief
谢谢,我认为另一种思考方式是:当我们将对象分类到层次结构中的IA和IB时,重要的是没有具有IA和IB属性的对象,否则会有钻石继承,和组合爆炸(如果有很多IC,ID ......),在这种情况下最好是作为一个行为接口来提供其他类的能力,这更灵活,对吧? - Baiyan Huang


我正在我的项目中处理类层次结构,基本上我有类似你在问题中描述的情况。

假设我有基类型Object,它是我工具箱中所有其他类的绝对根。所以一切都是直接或通过它的子类来源于它。每个Object派生类都必须提供一个共同的功能,但在某些叶子类中,效果与其他类别差别不大。例如,每个对象都有大小和位置,可以使用属性或方法更改,如Position = new Point(10,10),Width = 15等。但是有些类应该忽略属性的设置或根据自己修改它内心的状态。考虑控制停靠在父窗口的左侧。您可以设置所有喜欢的高度属性,但通常会忽略它,因为此属性实际上取决于父容器控件的高度(或者它的ClientArea高度或类似的高度)。

因此,在您到达需要“自定义”行为的位置之前,让Object抽象类实现基本的通用功能是可以的。如果Object提供在Height属性的setter中调用的受保护的虚拟SetHeight方法,则可以在DockedControl类中覆盖它,并且仅当对接为None时才允许更改高度,在其他情况下,您可以限制它或完全忽略。

所以我们很高兴,但现在我们需要对像Click或Hover等鼠标事件做出反应的对象。所以我们从抽象的Object类派生MouseAwareObject并实现事件和东西。

现在客户端需要可停靠的鼠标感知对象。所以我们从DockableObject派生出来......嗯,现在怎么样?如果我们可以做多重继承我们可以做到但是我们遇到了重复界面模糊的钻石问题,我们需要处理它。我们可以在新类中使用两个Dockable和MouseAware类型,并对它们进行代理外部调用以提供功能。

最后我想到的是制作IDockable和IMouseAware接口,让它们定义功能并将它们自由添加到需要提供具体行为/实现的对象中。

我想我会将我的基类分成几部分,并使我的对象具有非常有限的“核心”属性和方法集以及其他功能,这些功能实际上是对象作为类型可选的,但在具体情况下需要移动到像IResizable这样的接口, IDockable,IMakeAWorldABetterPlaceAble等。使用此解决方案,可以将行为“附加”到Object派生类,而无需从根基类一直到叶类的draggin虚拟或纯虚拟(抽象)方法。

在所有受影响的类中实现接口当然不方便,但您始终可以实现一些“适配器”并只是转发它们。这样你就没有重复的实现(当然在某种程度上)并且在任务的实现之间脱钩(Resize可能对不同的类意味着不同的东西)和客户端代码的期望。

可能这对你的问题不是一个理想的答案,但也许它会暗示你自己的解决方案。


1
2018-06-29 08:55



谢谢,grapkulec - Baiyan Huang


我认为你直接跳转到接口,跳过类。是否需要你的应用程序。有一个“IObject”接口?也许您的类层次结构的“CObject”根类可能对您有所帮助。

它认为获胜者是第一解决方案,你可能有一个“MyObject”,无论是接口的实现,还是直接的类。稍后,您可以根据需要在类层次结构中添加其他类或接口。

看到几个应用程序(一些我的,其他一些),我认为应该有一个“我的自定义应用程序类层次结构根对象”或“我的自定义应用程序类层次结构根接口”设计模式。


1
2018-06-22 15:31