web-dev-qa-db-ja.com

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

同様の質問がかなりあります 12 、- 、 4 ですが、この質問では正確には当てはまらないようであり、ソリューションが最適であるようにも見えません。

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

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

3 parallel class hierarchies, the centre column shows the common parts, the left column is the canvas hierarchy and the right column shows the SVG hierarchy

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

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

ソリューションI-継承をいじめる

FowlerのTease Apart Inheritanceは、2つの類似点の間にいくつかの不一致があるため、ここではうまくいかないようです。

ソリューションII-ミックスイン

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

The three columns again, the left and right columns are parallel hierarchies, where each class also inherent from a common class. The common classes are not part of an hierarchy

各列は独自の名前空間にあるため、クラス名が競合しないことに注意してください。

質問

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


補遺

これをどのように使用するかを示すサンプルコードを次に示します。名前空間svgcanvasに置き換えることができます。

_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 );
_

基本的に、runtimeクライアントでは、Vieweeサブクラスのインスタンス階層を構成します。そのようです:

An image showing a hierarchy of objects like layer, rect, scroller, etc.

これらのすべてのビュー対象はキャンバス階層からのものであり、階層をたどることによってレンダリングされ、各ビュー対象でPaint()を呼び出すことができます。それらがsvg階層からのものである場合、被閲覧者は自分をDOMに追加する方法を知っていますが、Paint()トラバーサルはありません。

12
Izhaki

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 {
}
5

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

それは実際にはまったく真実ではありません。 TypeScriptには、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です。使用する関数は、クラスの定義で使用される基本クラスまたはインターフェイスについて知らないか、単一のたわごとを与えないことに注意してください。

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

3
DeadMG

クイック概要

解決策3:「並列クラス階層」ソフトウェア設計パターンはあなたの友人です。

長い回答

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

同じ概念を数回、通常は制御階層で扱います。

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

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

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

これらの以前のソリューションを個別に適用した場合、それらはいくつかの問題を解決する方法です。

しかし、これら2つのソリューションを同時に組み合わせるとどうなるでしょうか。

それらを組み合わせると、この「デザインパターン」が得られます。

実装

次に、「並列クラス階層」ソフトウェア設計パターンをケースに適用してみましょう。

現在、2つ以上の独立したクラス階層があり、それらは非常に類似しており、類似の関連付けまたは目的があり、類似のプロパティまたはメソッドがあります。

コードまたはメンバーの重複(「一貫性」)を回避したいが、これらのクラスの違いにより、このクラスを直接単一のクラスにマージすることはできません。

したがって、階層はこの図とよく似ていますが、それでも複数あります。

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

Figure 1

これでは、まだ認定されていませんが、デザインパターン、いくつかの類似した階層が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」は、それぞれの「コンポジット」から派生するのではなく、共通の「Common :: Viewee」から派生することに注意してください。

今すぐメンバーを追加できます。

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

Figure 5

プロセスを繰り返す

プロセスは続行され、クラスごとに、「Canvas :: Visual」は「Canvas :: Viewee」から派生せず、「Commons :: Visual」から「Canvas :: Structural」は「Canvas :: Viewee」から派生しません"、" Commons :: Structural "のbuitなど。

4 3D階層図

いくつかのレイヤーで構成される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」では適用されません。継承がいずれの場合にも必要だからです。

「ソリューション2」、「ミックス」を適用できますが、クラスと階層を設計した後です。

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

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

多くの開発者/デザイナーはそれに同意せず、存在すべきではないと信じています。しかし、私は自分や他の開発者が、あなたの質問のような問題の一般的な解決策として使用しました。

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

3
umlcat

C++での二重継承階層を処理するための設計パターン というタイトルの記事で、ボブおじさんはStairway to Heavenという解決策を紹介しています。それは意図を述べています:

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

そして提供される図:

A class diagram with two parallel inheritance structures, where each class on the right also virtually inherent from its twin class on the left. The left hierarchy is also based completely on virtual inheritance

ソリューション2では仮想継承はありませんが、Stairway to Heavenパターンと非常によく一致しています。したがって、ソリューション2はこの問題に対して妥当であると思われます。

1
Izhaki