35

2つの並列継承チェーンがあります。

Vehicle <- Car
        <- Truck <- etc.

VehicleXMLFormatter <- CarXMLFormatter
                    <- TruckXMLFormatter <- etc.

私の経験では、並列継承階層は、成長するにつれてメンテナンスの頭痛の種になる可能性があります。

toXML(), toSoap(), toYAML()つまり、プリンシパルクラスにメソッドを追加しません。

関心の分離の概念を破ることなく、並列継承階層を回避するにはどうすればよいですか?

4

6 に答える 6

13

ビジターパターンの使用を考えています。

public class Car : Vehicle
{
   public void Accept( IVehicleFormatter v )
   {
       v.Visit (this);
   }
}

public class Truck : Vehicle
{
   public void Accept( IVehicleFormatter v )
   {
       v.Visit (this);
   }
}

public interface IVehicleFormatter
{
   public void Visit( Car c );
   public void Visit( Truck t );
}

public class VehicleXmlFormatter : IVehicleFormatter
{
}

public class VehicleSoapFormatter : IVehicleFormatter
{
}

これにより、余分な継承ツリーを回避し、書式設定ロジックを車両クラスから分離したままにします。もちろん、新しい車両を作成するときは、別のメソッドを Formatter インターフェースに追加する必要があります (そして、この新しいメソッドをフォーマッター インターフェースのすべての実装に実装します)。
しかし、これは新しい Vehicle クラスを作成するよりも優れていると思います。また、IVehicleFormatter ごとに、この新しい種類の車両を処理できる新しいクラスを作成します。

于 2009-03-30T08:14:13.827 に答える
9

もう 1 つのアプローチは、プル モデルではなくプッシュ モデルを採用することです。通常、カプセル化を破っているため、別のフォーマッターが必要であり、次のようなものがあります。

class TruckXMLFormatter implements VehicleXMLFormatter {
   public void format (XMLStream xml, Vehicle vehicle) {
      Truck truck = (Truck)vehicle;

      xml.beginElement("truck", NS).
          attribute("name", truck.getName()).
          attribute("cost", truck.getCost()).
          endElement();
...

特定のタイプからフォーマッタにデータをプルしている場所。

代わりに、形式に依存しないデータ シンクを作成し、フローを逆にして、特定の型がデータをシンクにプッシュするようにします。

class Truck  implements Vehicle  {
   public DataSink inspect ( DataSink out ) {
      if ( out.begin("truck", this) ) {
          // begin returns boolean to let the sink ignore this object
          // allowing for cyclic graphs.
          out.property("name", name).
              property("cost", cost).
              end(this);
      }

      return out;
   }
...

つまり、データはまだカプセル化されており、タグ付きデータをシンクにフィードしているだけです。次に、XML シンクはデータの特定の部分を無視し、一部を並べ替えて、XML を書き込みます。内部で別のシンク戦略に委任することもできます。ただし、シンクは必ずしも車両の種類を気にする必要はなく、何らかの形式でデータを表現する方法だけを気にする必要があります。インライン文字列ではなくインターンされたグローバル ID を使用すると、計算コストを抑えることができます (ASN.1 またはその他の厳密な形式を記述している場合にのみ重要です)。

于 2009-03-30T09:28:08.377 に答える
2

フォーマッタの継承を避けることができます。単純に、s、 s、...VehicleXmlFormatterを処理できる a を作成します。再利用は、メソッド間の責任を切り刻み、適切なディスパッチ戦略を考え出すことによって、簡単に達成できるはずです。魔法のオーバーロードは避けてください。フォーマッタでメソッドに名前を付ける際は、できるだけ具体的にしてください (例:の代わりに)。CarTruckformatTruck(Truck ...)format(Truck ...)

二重ディスパッチが必要な場合にのみ Visitor を使用してください: 型のオブジェクトがありVehicle、実際の具象型を知らなくてもそれらを XML にフォーマットしたい場合。Visitor 自体は、フォーマッターでの再利用を実現するという基本的な問題を解決するものではなく、必要のない余分な複雑さをもたらす可能性があります。上記のメソッドによる再利用のルール (チョッピングとディスパッチ) は、Visitor 実装にも適用されます。

于 2009-03-30T08:33:53.310 に答える
1

IXMLFormatter を toXML()、toSoap()、toYAML() メソッドのインターフェイスにして、Vehicle、Car、Truck のすべてにそれを実装させてみませんか? そのアプローチの何が問題なのですか?

于 2009-03-30T08:13:27.493 に答える