並列階層-部分的に同じ、部分的に異なる


12

似たような質問がたくさんあります 1234、ただし、この質問ではnonは当てはまらないようであり、解は最適とは思われません。

これは一般的なOOPの質問であり、多態性、ジェネリック、およびミックスインが利用可能であると仮定しています。実際に使用される言語はOOP Javascript(Typescript)ですが、JavaまたはC ++でも同じ問題です。

並列クラス階層があり、同じ動作(インターフェイスと実装)を共有することもありますが、それぞれに独自の「保護された」動作があります。次のように説明します。

3つの並列クラス階層。中央の列は共通部分を示し、左の列はキャンバスの階層、右の列はSVGの階層を示します

これは、説明のみを目的としています。実際のクラス図ではありません。それを読むには:

  • 共通階層(中心)のすべては、Canvas(左)階層とSVG(右)階層の両方で共有されます。シェアとは、インターフェースと実装の両方を意味します。
  • 左または右の列にのみあるものは、その階層に固有の動作(メソッドとメンバー)を意味します。例えば:
    • 左右の階層は、まったく同じ検証メカニズムを使用しViewee.validate()ます。これは、共通の階層で単一のメソッド()として示されています。
    • キャンバス階層のみにメソッドがありpaint()ます。このメソッドは、すべての子に対してpaintメソッドを呼び出します。
    • SVG階層はのaddChild()メソッドをオーバーライドする必要がありますCompositeが、キャンバス階層の場合はそうではありません。
  • 両側の階層の構造を混在させることはできません。工場はそれを保証します。

解決策I-継承を離れる

Fowler's Tease Apart Inheritanceは、2つの類似点の間に矛盾があるため、ここでは役に立たないようです。

解決策II-ミックスイン

これは私が現在考えることができる唯一のものです。2つの階層は別々に開発されますが、各レベルでクラスはクラス階層の一部ではない共通クラスに混在します。structuralフォークを省略すると、次のようになります。

再び3つの列、左と右の列は並列階層であり、各クラスは共通のクラスに固有です。 共通クラスは階層の一部ではありません

各列は独自の名前空間にあるため、クラス名は競合しません。

質問

誰でもこのアプローチで障害を見ることができますか?誰もがより良い解決策を考えることができますか?


補遺

これがどのように使用されるかを示すサンプルコードを次に示します。名前空間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()トラバーサルはありません。



フル機能のデコレータデザインパターン(Erich Gamma et al、Design Patterns)を試してみませんか?
ゾン

視聴者とは何ですか?「形容詞」とは対照的に、名詞としての「平行」とはどういう意味ですか?
Tulainsコルドバ

多重継承がありますか?
Tulainsコルドバ

CanvasクラスまたはSVGクラスには、共通ではない追加の状態またはデータが含まれていますか?クラスをどのように使用しますか?これらのhiearchiesをどのように使用できるかを示すコード例をいくつか示していただけますか?
陶酔

回答:


5

2番目のアプローチは、インターフェイス分離の原則に従って、インターフェイスをより適切に分離します。

ただし、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

ダウンボッターではありませんが、IMHOの指数関数的なインターフェイスは良いパターンではありません
-dagnelies

@arnaud「指数関数的インターフェース」とはどういう意味ですか?
Tulainsコルドバ

@ user61852 ...まあ、それはたくさんのインターフェースだと言ってみましょう。「指数」は実際には間違った用語であり、「乗法」に似ています。より多くの「ファセット」(複合、視覚、ペイント可能...)およびより多くの「要素」(キャンバス、svg ...)があれば、多くのインターフェースになります。
dagnelies

@arnaudポイントはありますが、少なくとも事前に柔軟な設計があり、OPの継承の悪夢は、拡張を余儀なくされたときに解決されます。必要に応じてクラスを拡張し、不自然な階層に強制されないようにします。
Tulainsコルドバ

3

これは一般的なOOPの質問であり、多態性、ジェネリック、およびミックスインが利用可能であると仮定しています。実際に使用される言語はOOP Javascript(Typescript)ですが、JavaまたはC ++でも同じ問題です。

それは実際にはまったく真実ではありません。タイプスクリプトには、Javaに比べて実質的な利点があります。つまり、構造型付けです。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());

これは完全に正当なTypescriptです。消費する関数は、クラスの定義で使用される基本クラスまたはインターフェイスについて、単一のたわごとを知らないか、または与えないことに注意してください。

クラスが継承によって関連しているかどうかは関係ありません。インターフェースを拡張しても問題ありません。インターフェースをパラメーターとして定義するだけで、完了です。それを満たすすべてのクラスが受け入れられます。


有望に思えますが、私はその提案を本当に理解していません(申し訳ありませんが、OOPバイアスが多すぎるかもしれません)。おそらく、いくつかのコード例を共有できますか?たとえば、svg.Vieweecanvas.Vieweeの両方にvalidate()メソッドが必要です(どちらの実装も同じです)。svg.VieweeのみがaddChild()をオーバーライドする必要があり、canvas.Vieweeのみがpaint()(ベースCompositeクラスのメンバーであるすべての子に対してpaint()を呼び出すのみを必要とします。したがって、構造タイピングではこれを実際に視覚化することはできません。
イザキ

この場合、まったく問題にならないことを考えています。
-DeadMG

だから私はおそらくまったく答えを得られなかった。あなたが詳しく説明してくれると嬉しいです。
イザキ

編集しました。肝心なのは、基本クラスは絶対に無関係であり、誰も気にしないということです。それらは実装の詳細のみです。
-DeadMG

1
OK。これは理にかなっています。A)アヒルのタイピングとは何かを知っています。B)インターフェイスがこの答えの中心である理由がわかりません-クラスの内訳は共通の振る舞いを共有するためであり、現時点ではインターフェイスを安全に無視できます。C)あなたが持っているあなたの例で言うがSVGViewee.addChild()CanvasVieweeまた全く同じ機能を必要とします。両方がコンポジットに固有であるということは私には理にかなっているように思えますか?
イザキ

3

クイック概要

解決策3:「Parallel Class Hierarchy」ソフトウェア設計パターンはあなたの友人です。

長い拡張回答

あなたのデザインはまさに始まりました。最適化でき、一部のクラスまたはメンバーを削除できますが、問題を解決するために適用する「並列階層」の考え方は正しいです。

通常は制御階層で、同じ概念を数回処理します。

しばらくして、「パラレル階層」デザインパターンまたは「デュアル階層」デザインパターンと呼ばれることもある他の開発者と同じソリューションを終了しました。

(1)単一のクラスを単一のクラス階層に分割したことがありますか?

(2)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。それらは「Hollow Classes」、「Enumerative Classes」、および他の名前と呼ばれます。

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」から派生せず、buitは「Commons :: Visual」から派生し、「Canvas :: Structural」は「Canvas :: Vieweeから派生しません」 "、" Commons :: Structural "からのbuitなど。

4 3D階層図

いくつかのレイヤー、最上位レイヤーには「共通」階層、最下位レイヤーには追加の階層があります。

元の独立したクラス階層。これに似たもの(図6):

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

Figure 6

いくつかのクラスは省略され、「Canvas」階層全体は省略されていることに注意してください。

最終的に統合されたクラス階層は、次のようなものになります。

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

いくつかのクラスが省略され、「Canvas」クラス全体が省略されていることに注意してください。ただし、「SVG」クラスに似ています。

「共通」クラスは、3Dダイアグラムの単一レイヤー、別のレイヤーの「SVG」クラス、および3番目のレイヤーの「キャンバス」クラスとして表すことができます。

各レイヤーが最初のレイヤーに関連していることを確認します。各クラスには、「共通」階層の親クラスがあります。

コードの実装では、プログラミング言語がサポートするものに応じて、インターフェイス継承、クラス継承、または「ミックスイン」のいずれかを使用する必要があります。

概要

プログラミングソリューションとして、最適化に突入しないでください。最適化は非常に重要ですが、最適化が悪いと、元の問題よりも大きな問題になる可能性があります。

「ソリューション1」または「ソリューション2」を適用することはお勧めしません。

「ソリューション1」では、継承が必要なため、適用されません。

「Solution 2」、「Mixins」を適用できますが、クラスと階層を設計した後です。

ミックスインは、インターフェイスベースの継承、またはクラスベースの多重継承の代替手段です。

私が提案したソリューション3は、「パラレル階層」デザインパターンまたは「デュアル階層」デザインパターンと呼ばれることもあります。

多くの開発者/設計者はそれに同意せず、存在すべきではないと考えます。しかし、私は自分自身や他の開発者を、あなたの質問のような問題の一般的な解決策として使用しました。

もう一つ欠けているもの。以前のソリューションでは、主な問題は「ミックスイン」や「インターフェース」を使用することではなく、まずクラスのモデルを改良し、後で既存のプログラミング言語機能を使用することでした。


非常に徹底的な回答をありがとう。私はそれを大丈夫だと思ったので、これを聞かせてください:canvas.vieweeそしてそのすべての子孫はと呼ばれるメソッドを必要としpaint()ます。クラスcommonsvgクラスもそれを必要としません。しかし、あなたのソリューションでは、階層がしているcommonではない、canvasまたはsvg正確に私の解決策2のように、どのようpaint()にすべてのサブクラスで終わるcanvas.viewee何の継承が存在しない場合は?
イザキ

@Izhaki回答にいくつかバグがある場合は申し訳ありません。次にpaint()、「canvas :: viewee」で移動または宣言する必要があります。一般的なパターンのアイデアは残りますが、一部のメンバーは移動または変更が必要になる場合があります。
umlcat

OK、サブクラスはどのように取得しcanvas::vieweeますか?
イザキ

ツールを使用してアスキーアートを作成しましたか?(ドットが実際に役立つかどうかはわかりません。価値があるからです。)
アーロンホール

1

C ++の二重継承階層に対処するためのデザインパターンというタイトルの記事で、ボブおじさんはStairway to Heavenと呼ばれるソリューションを紹介しています。意図が述べられています:

このパターンは、特定の階層全体を別のクラスに適合させる必要がある場合に必要な継承関係のネットワークを記述します。

そして、提供される図:

2つの並列継承構造を持つクラス図。右側の各クラスは、左側のツインクラスからも実質的に固有です。 左の階層も仮想継承に完全に基づいています

ソリューション2には仮想継承はありませんが、「天国への階段」パターンと非常に一致しています。したがって、解決策2はこの問題には妥当と思われます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.