web-dev-qa-db-ja.com

デコレータパターンと継承の例

たとえば、触れたくないコードの機能を拡張するためにデコレータパターンを実験してきましたが、その実装方法はわかりましたが、元のクラスから継承してそのように拡張しない理由がわかりません。

デコレータパターンを使用すると実行時に機能を追加できるのに対し、継承とはコンパイル時に機能を追加できることを意味します。

わかりません。

誰かがこれを説明し、例を提供し、デコレータと継承を使用する方が良い場合を説明できますか?.

ありがとう

31
Jon

アイテムを特定の方法で表示するViewクラスを作成するとします。ここで、スクロール可能なバージョンも必要であると判断したので、ビューを継承するScrollableViewを作成します。後で、境界線のあるバージョンも必要になると判断したため、BorderedViewとBorderdScrollableViewを作成する必要があります。

一方、追加されたスタイリングごとにデコレータを作成できる場合。次のクラスがあります。

  • 見る
  • ScrollableDecorator
  • BorderedDecorator

境界線のスクロールビューが必要な場合は、次のようにします。

new BorderedDecorator(new ScrollableDecorator(new View()))

したがって、3つのクラスだけでこれを任意に組み合わせて構成できます。また、実行時にそれらを追加または削除できます([境界線の追加]というボタンをクリックすると、ビューがBorderDecoratorでラップされます...継承がまだ行われていない場合は、このビュークラスを実装する必要があります。新しいビューインスタンスを作成し、関連するすべてのデータを最初のビューから2番目のビューにコピーする必要があります。これは、ラッパーを追加または削除するほど簡単ではありません)。

31
Razvi

Civilizationのようなゲームを想像してみてください。このゲームでは、マップ上の各正方形にさまざまなリソース(たとえば、さまざまな鉱石、木材、石油など)を添付できます。

ストレート継承を使用した場合は、正方形の種類ごとにクラスを作成する必要があります。持っているのは扱いにくいでしょう

public class OilSquare {}
public class OilAndGoldSquare {}
public class GoldAndSilverSquare {}
// etc.

デコレータパターンを使用すると、厳密な階層を作成しなくても、組み合わせて組み合わせることができます。したがって、代わりに次のようになります。

public class Square {}
public class GoldDec {}
public class SilverDec {}
public class OilDec {}

// ...

var crazyMix = new GoldDec(new SilverDec(new OilDec(new Square())));

言い換えると、デコレータはパイプライン動作の作成を可能にし、パイプラインの各ステップは別のステップと交換可能です。

27

他の人がすでに言っているように、デコレータは物事に「オプション」を追加するのに適しています...利点は、デコレータを介してメソッドなどをチェーンできる方法にあります。

革のインテリア、メタリックペイント、素晴らしいスポイラーのオプションを備えた車を購入すると想像してみてください...

3つのオプションには8つの異なる組み合わせがありますが、デコレータを使用すると、3つの追加クラスのみが必要になります。

ただし、興味深いのは、デコレータパターンの動作方法です。簡単な例として:

public class MetallicPaint : Car
{
    private Car car;
    public MetallicPaint(Car wrappedCar)
    {
        car = wrappedCar;
    }

    public decimal Cost()
    {
        return car.Cost() + 500;
    }

    public string Description()
    {
        return car.Description() + ", Metallic Paint";
    }
    public string Speed()
    {
        return car.Speed();
    }
    [... {pass through other methods and properties to the car object}]
}

これは完全な例ではありませんが、デコレータがデコレーションしているオブジェクトとどのように相互作用できるかを強調しています。そしてもちろん、それは車を実装しているので、他のすべての方法で車と同じように使用できます(そして、デコレータが車の内側のオブジェクトに影響を与えないものはすべて通過します)。

もちろん、これらのデコレータが複数あり、それぞれの中に車がネストされている場合は、コストが追加され、説明の一部とスポイラーが速度を変更しますが、他のデコレータは変更しませんでした...

本質的には、継承よりもはるかにモジュール化された、基本的ではない方法でオブジェクトを変更できます。デコレータは常にベースオブジェクト(この場合はCar)であるかのように使用する必要があるため、新しいメソッドやプロパティを公開することはなく、既存のものの効果をわずかに変更するだけです。

13
Chris

追加する機能が多く、これらの機能の組み合わせも必要な場合は、デコレータパターンが継承よりも優れています。基本クラスがAであり、この基本クラスを機能f1、f2、f3、f4と、(f1、f2)、(f1、f3)、..;などの組み合わせで拡張(装飾)したいとします。したがって、階層内に4!= 4 * 3 * 2 * 1 = 24クラスを作成する必要があります(各機能に4つ、それらの組み合わせに残り)。一方、装飾パターンを使用すると、4つのクラスを作成するだけで済みます!


@Razvi投稿の@SeyedMorteza Mousaviの場合:その通りです。2つのプロパティScrollableとBordered to Viewクラスを追加し、プロパティがtrueに設定されているかどうかを確認して、目的の動作を実行します。ただし、これには、必要な機能の数をすでに認識している必要があります(デコレータパターンの場合はそうではありません)。それ以外の場合は、クラスに追加するすべての新機能(f1など)で、メインクラスを変更するか、メインクラスを継承してプロパティを追加する必要があります。後者のアプローチを採用すると、機能の組み合わせを処理するコードの部分をさらに変更する必要があります(「疎結合」の経験則に従わないため、これは適切ではありません)。

お役に立てれば。

6
qartal