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


18

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

それで、デコレータを調べて、ウィキペディアの記事を見つけました。デコレータパターンの概念は理解できましたが、この文章に少し混乱しました。

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

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

OK、すべてのウィンドウに境界線を追加するように言ったら、オプションを許可するために元のWindowクラスに機能を追加するだけではどうですか?私の見方では、サブクラス化はクラスに特定の機能を追加したり、クラスメソッドをオーバーライドしたりするためのものです。既存のすべてのオブジェクトに機能を追加する必要がある場合、スーパークラスを変更するだけではいけないのはなぜですか?

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

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

「...変更は元のクラスのすべてのインスタンスに影響します」と言っている場所がわかりません-サブクラス化は親クラスをどのように変更しますか?それがサブクラス化のポイントではないでしょうか?

この記事は、多くのWikiのように、明確に書かれていないことを前提としています。その最後の行で、デコレータの有用性を確認できます-「...個々のオブジェクトに対して実行時に新しい動作を提供します。」

このパターンを読んでいなくても、個々のオブジェクトの実行時の動作を変更する必要がある場合、おそらく、この動作を有効または無効にするメソッドをスーパークラスまたはサブクラスに組み込みます。デコレータの有用性、そして初心者の考え方に欠陥がある理由を本当に理解してください。


編集に感謝します、Walter ... fyi、私は女性を除外するのではなく、非公式の挨拶として「男」を使用しました。
ジム

編集は、一般に挨拶を削除するだけです。質問で使用するのは標準のSO / SEプロトコルではありません[心配しないで、質問に直接飛び込むのは失礼だとは思わない]
ファレル

クール、ありがとう!それは彼がそれを「性別を削除する」というタグを付けただけで、私は女性嫌いだと思う人を嫌いだと思います!:)
ジム

:私は、「サービス」と呼ばれるこれらの法的手続きの事を避けるために頻繁にそれらを使用medium.com/@wrong.about/...
Zapadlo

回答:


13

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

デコレータパターンの主な利点は、サブクラス化よりも多くの組み合わせオプションを使用できることです。たとえば、ウィンドウが持つことができる10の異なる動作がある場合、これは、サブクラス化を使用して、すべての異なる組み合わせを作成する必要があることを意味します。
ただし、新しい動作を追加する場合はどうなりますか?

デコレータを使用すると、この動作を説明する新しいクラスを追加するだけで済みます。パターンを使用すると、残りのコードを変更せずにこれを効果的にドロップできます。
サブクラス化を使用すると、悪夢が手に入ります。
あなたが尋ねた質問の1つは、「サブクラス化は親クラスをどのように変更するのですか?」親クラスを変更するわけではありません。インスタンスとは、「インスタンス化」したオブジェクトを意味します(たとえば、newコマンドを使用してJavaまたはC#を使用している場合)。それが言及しているのは、これらの変更をクラスに追加するとき、実際にそれを必要としない場合でも、その変更がそこにある選択肢はありません。

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

興味深い点があります。同じ機能を複数回追加できます。したがって、たとえば、必要に応じて、ダブル、トリプル、または任意の量または境界線を持つウィンドウを作成できます。

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


2
おかげで、ファレル-「代わりに、フラグを使用してオン/オフを切り替えて、彼のすべての機能を単一のクラスに入れることができます...しかし、プロジェクトが成長するにつれてますます大きくなる単一のクラスになります」それは私のためにクリックさせました。問題の一部は、デコレーターが私にとって意味のあるほど大きなクラスに取り組んだことがないことだと思います。大きく考えて、私は間違いなくその恩恵を見ることができます...ありがとう!
ジム

また、封印されたクラスに関する部分は非常に理にかなっています...ありがとう!
ジム

3

サブクラス化でスクロールバーと境界線を追加する可能性を考慮してください。すべての可能性が必要な場合は、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回目は、実装に応じて、既に描画されたScrollbarを上書きする場合としない場合があります。そのため、派生コードは別のクラスの実装と密接に結びついており、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です)。


私の考えは、クラスを追加し続けることではなく、元の(サブ)クラスに機能性を追加することです。したがって、クラスWindow(object)では、scrollBar = true、border = falseなどがあります...ファレルの答えは、それがどこに間違っているのかを示しています-ただ肥大化したクラスなどにつながる...答えをありがとう、 +1!
ジム

まあ、そうするための担当者がいると+1!
ジム

2

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

あなたが言及した例は、実際ここでかなりきれいにカバーされています

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.