24

ウィキペディアのデコレータのデザイン パターンと、このサイトのコード例を読みました。

従来の継承は「is-a」パターンに従いますが、デコレータは「has-a」パターンに従います。そして、デコレータの呼び出し規約は、「スキン」より「スキン」のように見えます..「コア」よりも。例えば

I* anXYZ = new Z( new Y( new X( new A ) ) );

上記のコード例のリンクに示されているように。

しかし、私が理解していないいくつかの質問がまだあります。

  1. 「実行時に特定のオブジェクトの機能を拡張 (装飾) するためにデコレータ パターンを使用できます」という wiki の意味は何ですか? 「new ...(new... (new...))」は実行時の呼び出しであり、適切ですが、「AwithXYZ anXYZ;」です。コンパイル時の継承であり、悪いですか?

  2. コード例のリンクから、両方の実装でクラス定義の数がほぼ同じであることがわかります。「Head first design patterns」のような他のデザイン パターンの本を思い​​出します。彼らは例としてスターバズコーヒーを使用し、従来の継承は「クラスの爆発」を引き起こすと言います。コーヒーの組み合わせごとに、そのクラスを考え出すからです。

    でも、この場合デコレータも同じじゃないですか。デコレータ クラスが任意の抽象クラスを取り、それを装飾できる場合、爆発を防ぐことができると思いますが、コード例から、正確な数のクラス定義が得られます...

誰か説明してくれませんか?

4

5 に答える 5

71

たとえば、いくつかの抽象ストリームを取り上げて、それらに暗号化および圧縮サービスを提供したいと考えてみましょう。

デコレータを使用すると(疑似コード):

Stream plain = Stream();
Stream encrypted = EncryptedStream(Stream());
Stream zipped = ZippedStream(Stream());
Stream zippedEncrypted = ZippedStream(EncryptedStream(Stream());
Stream encryptedZipped = EncryptedStream(ZippedStream(Stream());

継承により、次のものが得られます。

class Stream() {...}
class EncryptedStream() : Stream {...}
class ZippedStream() : Stream {...}
class ZippedEncryptedStream() : EncryptedStream {...}
class EncryptedZippedStream() : ZippedStream {...}

1) デコレータを使用すると、必要に応じて実行時に機能を組み合わせることができます。各クラスは、機能の 1 つの側面 (圧縮、暗号化など) のみを処理します。

2) この単純な例では、デコレータを持つ 3 つのクラスと、継承を持つ 5 つのクラスがあります。次に、フィルタリングやクリッピングなどのサービスをさらに追加しましょう。デコレータを使用すると、フィルタリング -> クリッピング -> 圧縮 -> 暗号化など、考えられるすべてのシナリオをサポートするためにあと 2 つのクラスが必要になります。継承では、組み合わせごとにクラスを提供する必要があるため、最終的に数十のクラスになります。

于 2012-09-12T01:02:54.110 に答える
8

逆の順序で:

2) たとえば、実行時に任意の組み合わせが必要になる可能性のある 10 個の異なる独立した拡張機能を使用すると、10 個のデコレータ クラスがその役割を果たします。継承によってすべての可能性をカバーするには、 1024 個のサブクラスが必要です。そして、大規模なコードの冗長性を回避する方法はありません。

1) 実行時に選択できる 1024 のサブクラスがあるとします。必要なコードをスケッチしてみてください。オプションを選択または拒否する順序を指定できない場合があることに注意してください。また、インスタンスを拡張する前にしばらく使用する必要がある場合があることも覚えておいてください。さあ、試してみてください。比較すると、デコレータでそれを行うのは簡単です。

于 2012-09-12T01:05:03.813 に答える
3

それらが時々非常に似ている可能性があることは間違いありません。どちらのソリューションの適用性と利点は、状況によって異なります。

他の人は、あなたの 2 番目の質問に対する適切な回答で私を打ち負かしました。要するに、デコレータを組み合わせて、継承ではできないより多くの組み合わせを実現できるということです。

そのため、最初に焦点を当てます。

コンパイル時が悪く、実行時が良いと厳密に言うことはできません。柔軟性が異なるだけです。実行時に物事を変更できることは、一部のプロジェクトにとって重要な場合があります。これは、再コンパイルが遅くなる可能性があり、コンパイルできる環境にいる必要があるためです。

継承を使用できない例は、インスタンス化されたオブジェクトに機能を追加する場合です。ロギング インターフェイスを実装するオブジェクトのインスタンスが提供されたとします。

public interface ILog{
    //Writes string to log
    public void Write( string message );
}

ここで、多くのオブジェクトが関係する複雑なタスクを開始し、それぞれがログを記録するため、ログ オブジェクトを渡すとします。ただし、タスクからのすべてのメッセージにタスク名とタスク ID をプレフィックスとして付ける必要があります。関数を渡すか、Name と Id を渡し、すべての呼び出し元がその情報を前に保留するという規則に従うことを信頼するか、渡す前にログ オブジェクトを修飾して、他のオブジェクトが行うことを心配する必要がないようにすることができます。それは正しい

public class PrependLogDecorator : ILog{

     ILog decorated;

     public PrependLogDecorator( ILog toDecorate, string messagePrefix ){
        this.decorated = toDecorate;
        this.prefix = messagePrefix;
     }

     public void Write( string message ){
        decorated.Write( prefix + message );
     }
}

C# のコードで申し訳ありませんが、C++ を知っている人にはアイデアを伝えることができると思います。

于 2012-09-12T01:18:30.517 に答える
1

まず、私は C# 派で、しばらく C++ を扱っていませんでしたが、私がどこから来たのかを理解していただければ幸いです。

頭に浮かぶ良い例は aDbRepositoryと aCachingDbRepositoryです。

public interface IRepository {
  object GetStuff();
}

public class DbRepository : IRepository {

  public object GetStuff() {
    //do something against the database
  }
}

public class CachingDbRepository : IRepository {
  public CachingDbRepository(IRepository repo){ }

  public object GetStuff() {
    //check the cache first
    if(its_not_there) {
      repo.GetStuff();
    }
}

したがって、継承を使用した場合は、DbRepositoryと がありCachingDbRepositoryます。はDbRepositoryデータベースからクエリを実行します。CachingDbRepositoryキャッシュをチェックし、データが存在しない場合はデータベースにクエリを実行します。したがって、ここで重複した実装が可能です。

デコレータ パターンを使用することで、同じ数のクラスを保持できますが、キャッシュにない場合は、CachingDbRepositoryを取り込んでIRepository呼び出して、基になるリポジトリからデータを取得します。GetStuff()

したがって、クラスの数は同じですが、クラスの使用は関連しています。 CachingDbRepoそれに渡されたレポを呼び出します...したがって、継承よりも構成に似ています。

装飾よりも継承のみを使用する時期をいつ決定するかは、主観的なものだと思います。

これが役立つことを願っています。幸運を!

于 2012-09-12T01:01:24.110 に答える
1

質問の 2 番目の部分に対処するには (最初の部分に対処する場合もあります)、デコレータ メソッドを使用すると、同じ数の組み合わせにアクセスできますが、それらを記述する必要はありません。各レベルに 5 つのオプションを持つ 3 つのレイヤーのデコレーターがある場合、5*5*5継承を使用して定義できるクラスがあります。デコレータ メソッドを使用すると、15 が必要になります。

于 2012-09-12T00:53:29.433 に答える