并行层次结构-部分相同,部分不同


12

那里有很多类似的问题 1234,但是在这个问题上似乎并非如此,解决方案也不是最优的。

假设多态性,泛型和混合可用,这是一个普遍的OOP问题。实际使用的语言是OOP Javascript(Typescript),但是在Java或C ++中,这是同样的问题。

我有并行的类层次结构,有时具有相同的行为(接口和实现),但有时每个都有其自己的“受保护”行为。像这样说明:

3个并行类层次结构,中间一列显示公共部分,左列为画布层次结构,右列显示SVG层次结构

是为了说明目的 ; 这不是实际的类图。阅读:

  • Canvas(左)和SVG(右)层次结构共享公共层次结构(中心)中的任何内容。分享是指接口和实现。
  • 仅左列或右列中的任何内容均表示特定于该层次结构的行为(方法和成员)。例如:
    • 左和右层次结构都使用完全相同的验证机制,Viewee.validate()在通用层次结构上显示为单个方法()。
    • 仅画布层次结构具有方法paint()。此方法在所有子项上调用paint方法。
    • SVG层次结构需要覆盖的addChild()方法Composite,但是canvas层次结构并非如此。
  • 不能从两个侧面层次结构中混合构造。工厂确保做到这一点。

解决方案I-逗弄继承

Fowler的“逗趣分开继承”在这里似乎不起作用,因为两个相似之处之间存在一些差异。

解决方案II-Mixins

这是我目前唯一能想到的。这两个层次结构是分别开发的,但是在每个级别上,这些类都混合在公共类中,而这并不属于类层次结构。省略structural叉子,将如下所示:

同样的三列,左列和右列是并行的层次结构,其中每个类也来自同一类。 通用类不是层次结构的一部分

请注意,每列将位于其自己的名称空间中,因此类名不会冲突。

问题

谁能看到这种方法的缺点?谁能想到更好的解决方案?


附录

这是一些示例代码,该如何使用。命名空间svg可以替换为canvas

var iView        = document.getElementById( 'view' ),
    iKandinsky   = new svg.Kandinsky(),
    iEpigone     = new svg.Epigone(),
    iTonyBlair   = new svg.TonyBlair( iView, iKandinsky ),
    iLayer       = new svg.Layer(),
    iZoomer      = new svg.Zoomer(),
    iFace        = new svg.Rectangle( new Rect( 20, 20, 100, 60) ),
    iEyeL        = new svg.Rectangle( new Rect( 20, 20, 20, 20) ),
    iEyeR        = new svg.Rectangle( new Rect( 60, 20, 20, 20) );

iKandinsky.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iEpigone.setContext( iTonyBlair.canvas.getContext( '2d' ) );

iFace.addChildren( iEyeL, iEyeR );
iZoomer.setZoom( new Point( 2, 2 ) );
iZoomer.addChild( iFace );
iLayer.addChild( iZoomer );
iTonyBlair.setContent( iLayer );

本质上,在运行时客户端构成Viewee子类的实例层次结构。像这样:

该图像显示了对象的层次结构,例如图层,矩形,滚动条等。

说所有这些查看者都来自画布层次结构,它们是通过遍历层次结构可以paint()在每个查看者上调用而呈现的。如果他们来自svg层次结构,则查看者知道如何将自己添加到DOM,但不会paint()遍历。



也许尝试功能齐全的Decorator设计模式(Erich Gamma等人,设计模式)?
Zon

什么是观看者?“平行”作为名词(相对于形容词)是什么意思?
TulainsCórdova'16

您有多重继承吗?
TulainsCórdova'16

Canvas或SVG类是否包含非Common的其他状态或数据?您如何使用这些课程?您能否显示一些示例代码来说明如何使用这些层次结构?
欣快感'16

Answers:


5

第二种方法遵循接口隔离原则,可以更好地隔离接口。

但是,我将添加一个Paintable接口。

我还要更改一些名称。无需造成混乱:

// common

public interface IComposite {
    public void addChild(Composite e);
}

public interface IViewee extends IComposite{
    public void validate();
    public List<IBound> getAbsoluteBouns();
}

public interface IVisual {
    public List<IBound> getBounds();
}

public interface IRec {
}

public interface IPaintable {
    public void paint();
}

// canvas

public interface ICanvasViewee extends IViewee, IPaintable {
}

public interface ICanvasVisual extends IViewee, IVisual {
}

public interface ICanvasRect extends ICanvasVisual, IRec {
}


// SVG

public interface ISVGViewee extends IViewee {
    public void element();
}

public interface ISVGVisual extends IVisual, ISVGViewee {
}

public interface ISVGRect extends ISVGVisual, IRect {
}

我认为接口可能会帮助这种情况。我想知道您的答案被否决的原因。
umlcat

不是
下降投票者

@arnaud“指数接口”是什么意思?
TulainsCórdova'16

@ user61852 ...好吧,我们只说它有很多接口。“指数”实际上是一个错误的术语,它更像是“乘法”。从某种意义上说,如果您拥有更多的“构面”(复合,视觉,可绘画...)和更多的“要素”(画布,svg ...),那么您最终将拥有许多接口。
dagnelies,2013年

@arnaud您有一点要说,但至少事先有一个灵活的设计,当您不感到被迫扩展时,OP的继承梦night将得到解决。如果需要,可以扩展某些类,而不必受人为的层次结构的强迫。
TulainsCórdova'16

3

假设多态性,泛型和混合可用,这是一个普遍的OOP问题。实际使用的语言是OOP Javascript(Typescript),但是在Java或C ++中,这是同样的问题。

实际上根本不是真的。Typescript相对于Java具有显着的优势-即结构化类型。您可以使用Duck类型的模板在C ++中执行类似的操作,但是要付出更多的努力。

基本上,定义您的类,但不要理会扩展或定义任何接口。然后,只需定义所需的接口并将其作为参数即可。然后,对象可以匹配该接口-他们不需要事先知道就可以扩展它。每个函数都可以准确地声明,并且只有它们声明的位可以声明,即使最终类型实际上不扩展这些接口,编译器也会在最终类型满足时给您通过。

这使您无需真正定义接口层次结构并定义哪些类应扩展哪些接口。

只需定义每个类,而无需考虑接口,结构化类型将解决这个问题。

例如:

class SVGViewee {
    validate() { /* stuff */ }
    addChild(svg: SVG) { /* stuff */ }
}
class CanvasViewee {
    validate() { /* stuff */ }
    paint() { /* stuff */ }
}
interface SVG {
    addChild: { (svg: SVG): void };
}
f(viewee: { validate: { (): boolean }; }) {
    viewee.validate();
}
g(svg: SVG) {
    svg.addChild(svg);
}
h(canvas: { paint: { (): void }; }) {
    canvas.paint();
}
f(SVGViewee());
f(CanvasViewee());
g(SVGViewee());
h(CanvasViewee());

这是完全合法的打字稿。请注意,使用函数并不知道或仅给出有关类定义中使用的基类或接口的信息。

这些类是否通过继承进行关联并不重要。他们是否扩展了您的界面并不重要。只需将接口定义为参数,就可以了-满足该条件的所有类都将被接受。


听起来很有希望,但我不太理解该建议(对不起,可能是由于OOP偏见过多)。也许您可以分享一些代码示例?例如,svg.Vieweecanvas.Viewee都需要一个validate()方法(两者的实现是相同的)。那么只有svg.Viewee才需要重写addChild(),而只有canvas.Viewee才需要paint()(在所有子级上都调用paint(),它们是Composite类的基本成员)。因此,我无法真正通过结构化类型来可视化它。
Izhaki '16

您正在考虑一堆在这种情况下完全无关紧要的事情。
DeadMG '16

所以我可能根本没有得到答案。如果您详细说明,那就太好了。
Izhaki '16

我进行了编辑。底线是基类是绝对无关的,没有人关心它们。它们只是实现细节。
DeadMG '16

1
好。这开始变得有意义。A)我知道鸭子是什么类型的。B)我不确定为什么接口在这个答案中如此重要-类分解是为了共享常见的行为,您现在可以放心地忽略接口。C)在您的示例中说您有SVGViewee.addChild(),但CanvasViewee也需要完全相同的功能。因此,对于我来说,复合材料固有的两个特性似乎很有意义?
Izhaki '16

3

快速概述

解决方案3:“并行类层次结构”软件设计模式是您的朋友。

长扩展答案

您的设计从正确开始。可以对其进行优化,可以删除某些类或成员,但是,用于解决问题的“并行层次结构”构想是正确的。

多次处理相同的概念,通常是在控制层次结构中。

一段时间之后,我结束了与其他开发人员的相同解决方案,有时称为“并行层次结构”设计模式或“双重层次结构”设计模式。

(1)您是否曾经将单个类拆分为单个类层次结构?

(2)您是否曾经将一个班级分为几个班级,却没有层次结构?

如果您分别应用了这些先前的解决方案,那么它们是解决某些问题的一种方法。

但是,如果我们同时将这两种解决方案结合起来怎么办?

组合它们,您将获得此“设计模式”。

实作

现在,让我们将“并行类层次结构”软件设计模式应用于您的案例。

当前,您具有2个或多个独立的类层次结构,这些层次结构非常相似,具有相似的关联或目的,具有相似的属性或方法。

您希望避免重复的代码或成员(“一致性”),但是由于它们之间的差异,您无法将这些类直接合并为一个类。

因此,您的层次结构与该图非常相似,但是有多个:

................................................
...............+----------------+...............
...............|     Common::   |...............
...............|    Composite   |...............
...............+----------------+...............
...............|      ...       |...............
...............+-------+--------+...............
.......................|........................
.......................^........................
....................../.\.......................
.....................+-+-+......................
.......................|........................
...............+-------+--------+...............
...............|     Common::   |...............
...............|     Viewee     |...............
...............+----------------+...............
...............|      ...       |...............
...............+-------+--------+...............
.......................|........................
.......................^........................
....................../.\.......................
.....................+-+-+......................
.......................|........................
..........+------------+------------+...........
..........|.........................|...........
..+-------+--------+........+-------+--------+..
..|     Common::   |........|     Common::   |..
..|     Visual     |........|   Structural   |..
..+----------------+........+----------------+..
..|      ...       |........|      ...       |..
..+----------------+........+----------------+..
................................................

Figure 1

在此尚未认证的设计模式中,将几个相似的层次结构合并为一个单一的层次结构,并且每个共享或公共类都通过子类扩展。

请注意,此解决方案很复杂,因为您已经在处理多个层次结构,因此是一个复杂的方案。

1根类

在每个层次结构中,都有一个共享的“根”类。

对于您的情况,每个层次结构都有一个独立的“复合”类,该类可以具有一些相似的属性和一些相似的方法。

其中一些成员可以合并,某些成员不能合并。

因此,开发人员可以做的是创建一个根基础类,并为每个层次结构等效的子类。

在图2中,您可以看到仅用于该类的图,其中每个类都将其保留为名称空间。

现在,成员已被省略。

................................................
...............+-------+--------+...............
...............|     Common::   |...............
...............|    Composite   |...............
...............+----------------+...............
...............|      ...       |...............
...............+-------+--------+...............
.......................|........................
.......................^........................
....................../.\.......................
.....................+-+-+......................
.......................|........................
..........+------------+------------+...........
..........|.........................|...........
..+-------+--------+........+-------+--------+..
..|     Canvas::   |........|      SVG::     |..
..|    Composite   |........|    Composite   |..
..+----------------+........+----------------+..
..|      ...       |........|      ...       |..
..+----------------+........+----------------+..
................................................

Figure 2

您可能会注意到,每个“复合”类都不再位于单独的层次结构中,而是合并为单个共享或公共层次结构。

然后,让我们添加成员,将相同的成员移至超类,将不同的成员移至每个基类。

如您所知,“虚拟”或“重载”方法在基类中定义,但在子类中被替换。如图3。

................................................
.............+--------------------+.............
.............|       Common::     |.............
.............|      Composite     |.............
.............+--------------------+.............
.............| [+] void AddChild()|.............
.............+---------+----------+.............
.......................|........................
.......................^........................
....................../.\.......................
.....................+-+-+......................
.......................|........................
..........+------------+------------+...........
..........|.........................|...........
..+-------+--------+........+-------+--------+..
..|     Canvas::   |........|      SVG::     |..
..|    Composite   |........|    Composite   |..
..+----------------+........+----------------+..
..|      ...       |........|      ...       |..
..+----------------+........+----------------+..
................................................

Figure 3

请注意,也许有些类没有成员,并且您可能会想删除那些类,即DONT。它们被称为“空心类”,“枚举类”和其他名称。

2子类

让我们回到第一个图表。每个“复合”类在每个层次结构中都有一个“ Viewee”子类。

对每个班级都重复此过程。注意,除了图4之外,“ Common :: Viewee”类是从“ Common :: Composite”派生的,但为简单起见,图中省略了“ Common :: Composite”类。

................................................
.............+--------------------+.............
.............|       Common::     |.............
.............|       Viewee       |.............
.............+--------------------+.............
.............|        ...         |.............
.............+---------+----------+.............
.......................|........................
.......................^........................
....................../.\.......................
.....................+-+-+......................
.......................|........................
..........+------------+------------+...........
..........|.........................|...........
..+-------+--------+........+-------+--------+..
..|     Canvas::   |........|      SVG::     |..
..|     Viewee     |........|     Viewee     |..
..+----------------+........+----------------+..
..|      ...       |........|      ...       |..
..+----------------+........+----------------+..
................................................

Figure 4

您会注意到,“ Canvas :: Viewee”和“ SVG :: Viewee”不再源自其各自的“ Composite”,而是源自常见的“ Common :: Viewee”。

您现在可以添加成员。

......................................................
.........+------------------------------+.............
.........|            Common::          |.............
.........|            Viewee            |.............
.........+------------------------------+.............
.........| [+] bool Validate()          |.............
.........| [+] Rect GetAbsoluteBounds() |.............
.........+-------------+----------------+.............
.......................|..............................
.......................^..............................
....................../.\.............................
.....................+-+-+............................
.......................|..............................
..........+------------+----------------+.............
..........|.............................|.............
..+-------+---------+........+----------+----------+..
..|      Canvas::   |........|         SVG::       |..
..|      Viewee     |........|        Viewee       |..
..+-----------------+........+---------------------+..
..|                 |........| [+] Viewee Element  |..
..+-----------------+........+---------------------+..
..| [+] void Paint()|........| [+] void addChild() |..
..+-----------------+........+---------------------+..
......................................................

Figure 5

3重复该过程

对于每个类,该过程将继续进行,“ Canvas :: Visual”将不会从“ Canvas :: Viewee”开始,“ Commons :: Visual”,“ Canvas :: Structural”不会从“ Canvas :: Viewee”开始”,“ Commons :: Structural”中的内容等等。

4 3D层次结构图

您将完成3D图的排序,该图具有多个层,顶层是“公共”层次结构,底层是每个附加层次结构。

您最初的独立类层次结构,类似于以下内容(图6):

.................................................
..+-----------------+.......+-----------------+..
..|      Common::   |.......|       SVG::     |..
..|     Composite   |.......|     Composite   |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+--------+--------+..
...........|.........................|...........
...........^.........................^...........
........../.\......................./.\..........
.........+-+-+.....................+-+-+.........
...........|.........................|...........
..+--------+--------+.......+--------+--------+..
..|      Common::   |.......|       SVG::     |..
..|      Viewee     |.......|      Viewee     |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+--------+--------+..
...........|.........................|...........
...........^.........................^...........
........../.\......................./.\..........
.........+-+-+.....................+-+-+.........
...........|.........................|...........
..+--------+--------+.......+--------+--------+..
..|      Common::   |.......|       SVG::     |..
..|      Visual     |.......|      Visual     |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+--------+--------+..
...........|.........................|...........
...........^.........................^...........
........../.\......................./.\..........
.........+-+-+.....................+-+-+.........
...........|.........................|...........
..+--------+--------+.......+--------+--------+..
..|      Common::   |.......|       SVG::     |..
..|       Rect      |.......|       Rect      |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+-----------------+.......+-----------------+..
.................................................

Figure 6

请注意,为简单起见,省略了一些类,并省略了整个“画布”层次结构。

最终的集成类层次结构可能与此类似:

.................................................
..+-----------------+.../+..+-----------------+..
..|      Common::   +--<.+--+       SVG::     |..
..|     Composite   |...\+..|     Composite   |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+-----------------+..
...........|.....................................
...........^.....................................
........../.\....................................
.........+-+-+...................................
...........|.....................................
..+--------+--------+.../+..+-----------------+..
..|      Common::   +--<.+--+       SVG::     |..
..|      Viewee     |...\+..|      Viewee     |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+-----------------+..
...........|.....................................
...........^.....................................
........../.\....................................
.........+-+-+...................................
...........|.....................................
..+--------+--------+.../+..+-----------------+..
..|      Common::   +--<.+--+       SVG::     |..
..|      Visual     |...\+..|      Visual     |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+--------+--------+.......+-----------------+..
...........|.....................................
...........^.....................................
........../.\....................................
.........+-+-+...................................
...........|.....................................
..+--------+--------+.../+..+-----------------+..
..|      Common::   +--<.+--+       SVG::     |..
..|       Rect      |...\+..|       Rect      |..
..+-----------------+.......+-----------------+..
..|       ...       |.......|       ...       |..
..+-----------------+.......+-----------------+..
.................................................
Figure 7

请注意,为简单起见,省略了一些类,并省略了整个“画布”类,但它们与“ SVG”类相似。

“公共”类可以表示为3D图的单层,另一层可以表示为“ SVG”类,第三层可以表示为“画布”类。

检查每个层是否与第一个层相关,其中每个类都有一个“公共”层次结构的父类。

代码实现可能需要使用接口继承,类继承或“ mixins”,具体取决于您的编程语言支持的内容。

摘要

与任何编程解决方案一样,不要急于优化,优化非常重要,但是,糟糕的优化可能会比原来的问题成为更大的问题。

我不建议应用“解决方案1”或“解决方案2”。

“解决方案1”不适用于“解决方案1”,因为在每种情况下都需要继承。

可以应用“解决方案2”,“ Mixins”,但是在设计了类和层次结构之后。

Mixins是基于接口的继承或基于类的多重继承的替代方法。

我提出的解决方案3有时被称为“并行层次结构”设计模式或“双重层次结构”设计模式。

许多开发人员/设计师不同意它,并认为它不应该存在。但是,我自己和其他开发人员已将其用作问题的通用解决方案,例如您的问题之一。

另一个缺少的东西。在以前的解决方案中,主要问题不是立即使用“ mixins”或“接口”,而是首先要优化类的模型,然后再使用现有的编程语言功能。


感谢您的彻底答复。我想我很好理解,所以让我问这个:canvas.viewee并且它的所有后代都需要一个名为的方法paint()commonsvg类都不需要它。但是,在您的解决方案,该层次是common,没有canvassvg像我的解决方案2.那么究竟如何paint()在所有子类结束了canvas.viewee,如果没有继承呢?
Izhaki

@Izhaki对不起,我的回答中有几个错误。然后paint()应在“ canvas :: viewee”中移动或声明。通用模式的想法仍然存在,但是可能需要移动或更改某些成员。
umlcat

好的,如果没有子类从中派生,那么子类如何获得它canvas::viewee呢?
Izhaki '16

您是否使用过一种工具来创建您的Ascii艺术?(我不确定这些点对它的价值有实际帮助。)
Aaron Hall

1

在Bob的标题为“用C ++处理双重继承层次结构的设计模式”的文章中,鲍勃叔叔提出了一种名为“ 通往天堂的阶梯”的解决方案。声明意图:

这种模式描述了继承关系的网络,当必须将给定的层次结构整体上完全适应另一个类时,就需要使用这种继承关系。

并提供了该图:

具有两个并行继承结构的类图,其中右侧的每个类实际上也从左侧的孪生类继承而来。 左层次结构也完全基于虚拟继承

尽管在解决方案2中没有虚拟继承,但这与“ 通往天堂阶梯”模式非常一致。因此,解决方案2对于这个问题似乎是合理的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.