web-dev-qa-db-ja.com

デコレーターのデザインパターンに関する初心者の質問

私はプログラミングの記事を読んでいて、Decoratorパターンについて触れていました。私はしばらくプログラミングをしてきましたが、正式な教育やトレーニングは一切受けていませんが、標準的なパターンなどについて学びたいと思っています。

そこで、Decoratorを調べたところ、 Wikipediaの記事 が見つかりました。私は今、Decoratorパターンの概念を理解しましたが、この一節に少し混乱しました。

例として、ウィンドウシステムのウィンドウについて考えます。ウィンドウのコンテンツのスクロールを可能にするために、必要に応じて、ウィンドウに水平または垂直のスクロールバーを追加することができます。ウィンドウはWindowクラスのインスタンスによって表され、このクラスにはスクロールバーを追加する機能がないと想定します。それらを提供するScrollingWindowサブクラスを作成したり、既存のWindowオブジェクトにこの機能を追加するScrollingWindowDecoratorを作成したりできます。この時点で、どちらのソリューションでも問題ありません。

次に、ウィンドウに境界線を追加する機能も必要だとします。ここでも、元のWindowクラスはサポートしていません。 ScrollingWindowサブクラスは、新しい種類のウィンドウを効果的に作成したため、問題が発生します。すべてのウィンドウに境界線サポートを追加する場合は、サブクラスWindowWithBorderおよびScrollingWindowWithBorderを作成する必要があります。明らかに、この問題は、新しい機能が追加されるたびに悪化します。デコレーターソリューションの場合、新しいBorderedWindowDecoratorを作成するだけです。実行時に、ScrollingWindowDecoratorまたはBorderedWindowDecorator、あるいはその両方で既存のウィンドウを装飾できます。

すべてのウィンドウに境界線を追加するように言われたら、オプションを使用できるように機能を元のWindowクラスに追加してみませんか?私の見たところ、サブクラス化は、クラスに特定の機能を追加するため、またはクラスメソッドをオーバーライドするためだけのものです。既存のすべてのオブジェクトに機能を追加する必要がある場合、なぜスーパークラスを変更して追加しないのですか?

記事には別の行がありました:

デコレータパターンは、サブクラス化の代替手段です。サブクラス化はコンパイル時に動作を追加し、変更は元のクラスのすべてのインスタンスに影響します。デコレートは、実行時に個々のオブジェクトに新しい動作を提供できます。

彼らが「...変更は元のクラスのすべてのインスタンスに影響を与える」と言うところがわかりません-サブクラス化は親クラスをどのように変更しますか?それがサブクラス化の要点ではありませんか?

多くのWikiのように、記事が明確に書かれていないと仮定します。その最後の行で、Decoratorの有用性を確認できます。「実行時に個々のオブジェクトに新しい動作を提供します」。

このパターンについて読んだことがなければ、個々のオブジェクトの実行時に動作を変更する必要がある場合、おそらく、その動作を有効/無効にするためにスーパークラスまたはサブクラスにいくつかのメソッドを組み込んでいたでしょう。私がDecoratorの有用性を本当に理解するのを手伝ってください、そして、私の初心者の考えがなぜ欠陥があるのですか?

18
Jim

デコレータパターンは、継承よりも構成を優先するものです[別のOOPについて学ぶのに役立つパラダイム])

デコレータパターンの主な利点-サブクラス化よりも、ミックスとマッチのオプションを増やすことができます。たとえば、ウィンドウが持つことができる10の異なる動作がある場合、これは-サブクラス化を使用して-さまざまな組み合わせをすべて作成する必要があり、必然的に多くのコードの再利用も含まれます。
しかし、新しい行動を追加することを決定した場合はどうなりますか?

デコレーターを使用すると、この動作を記述する新しいクラスを追加するだけです。それだけです-パターンを使用すると、コードの残りの部分を変更することなく、これを効果的にドロップできます。
サブクラス化を使用すると、手に悪夢があります。
あなたが尋ねた一つの質問は、「サブクラス化は親クラスをどのように変更するのですか?」親クラスを変更するわけではありません。インスタンスと表示されている場合は、「インスタンス化」したオブジェクトを意味します(Javaまたはnewコマンドを使用してC#などを使用している場合)]。それが参照していることは、これらの変更をクラスに追加すると、実際にそれが必要でなくても、その変更をそこに含めるための選択肢がないということです。

または、フラグを使用してオン/オフすることで、彼のすべての機能を単一のクラスに入れることもできますが、プロジェクトが成長するにつれて、単一のクラスが次第に大きくなります。
この方法でプロジェクトを開始し、有効なクリティカルマスに達したら、デコレータパターンにリファクタリングすることは珍しいことではありません。

注意すべき興味深い点:同じ機能を複数回追加できます。そのため、たとえば、必要に応じて、二重、三重などの枠や境界線を備えたウィンドウを作成できます。

パターンの主なポイントは、実行時の変更を有効にすることです。プログラムが実行されるまで、ウィンドウの外観をどのようにしたいかわからない場合があり、これにより簡単にウィンドウを変更できます。確かに、これはサブクラス化によって実行できますが、それほどうまくいきません。
そして最後に、編集できない可能性があるクラスに機能を追加できるようにします-たとえば、密封された/最終的なクラス、または他のAPIから提供されるクラス

13
Farrell

サブクラス化でスクロールバーとボーダーを追加する可能性を検討してください。すべての可能性が必要な場合は、4つのクラス(Python)を取得します。

_class Window(object):
    def draw(self):
        "do actual drawing of window"

class WindowWithScrollBar(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of scrollbar"

class WindowWithBorder(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of border"

class WindowWithScrollBarAndBorder(Window):
    def draw(self):
        WindowWithScrollBar.draw(self)
        WindowWithBorder.draw(self)
_

現在、WindowWithScrollBarAndBorder.draw()では、ウィンドウが2回描画され、2回目には実装に応じて、既に描画されているスクロールバーを上書きする場合としない場合があります。したがって、派生コードは別のクラスの実装と密に結合されており、Windowクラスの動作を変更するたびにそのことに注意する必要があります。解決策は、派生クラスのスーパークラスからコードをコピーして貼り付け、派生クラスのニーズに合わせて調整することですが、スーパークラスのすべての変更を再度コピーアンドペーストして再度調整する必要があるため、派生クラスも基本クラスに密に結合されている(コピー、貼り付け、調整の必要性を介して)。別の問題は、ウィンドウが持つ、または持たない可能性があるさらに別のプロパティが必要な場合は、すべてのクラスを2倍にする必要があることです。

_class Window(object):
    ...

class WindowWithGimmick(Window):
    ...

class WindowWithScrollBar(Window):
    ...

class WindowWithBorder(Window):
    ...

class WindowWithScrollBarAndBorder(Window):
    ...

class WindowWithScrollBarAndGimmick(Window):
    ...

class WindowWithBorderAndGimmick(Window):
    ...

class WindowWithScrollBarAndBorderAndGimmick(Window):
    ...
_

これは、| f |を使用して相互に独立した機能fのセットを意味します。特徴の数であるため、2 ** | f |を定義する必要があります。クラス、例えば10個の機能がある場合は、1024個の密集したクラスを取得します。デコレータパターンを使用すると、すべての機能が独自の独立した疎結合クラスになり、1 + | f |しかありません。クラス(上記の例では11です)。

3
pillmuncher

私はこの特定のパターンの専門家ではありませんが、それを見ると、Decoratorパターンは、変更またはサブクラス化できない可能性があるクラスに適用できます(たとえば、コードではなく、シールされている可能性があります) )。あなたの例では、もしあなたがWindowクラスを書かずにそれを消費しているだけだったらどうでしょう? Windowクラスにインターフェイスがあり、そのインターフェイスに対してプログラムを作成している限り、Decoratorは同じインターフェイスを使用できますが、機能は拡張されます。

あなたが言及する例は実際にカバーされています here かなりきれいに:

オブジェクトの機能の拡張は、継承を使用して静的に(コンパイル時に)実行できますが、オブジェクトの使用時にオブジェクトの機能を動的に(実行時に)拡張する必要がある場合があります。

グラフィカルウィンドウの典型的な例を考えてみましょう。たとえばウィンドウにフレームを追加するなどしてグラフィカルウィンドウの機能を拡張するには、FramedWindowクラスを作成するためにウィンドウクラスを拡張する必要があります。フレーム付きウィンドウを作成するには、FramedWindowクラスのオブジェクトを作成する必要があります。ただし、プレーンウィンドウで開始し、実行時にその機能を拡張してフレームウィンドウにすることは不可能です。

http://www.oodesign.com/decorator-pattern.html

2
Dan Diplo