デコレーターパターンを使用して大きなオブジェクトに小さな機能を追加する方法は?


8

この質問では、Decoratorパターンを使用して、大きなクラスのオブジェクトにほとんど機能を追加しないようにしています。

古典的なDecoratorパターンに従って、次のクラス構造を検討してください:

ここに画像の説明を入力してください

たとえば、これがゲーム内で発生するとします。のインスタンスは、「ラッピング」しているConcreteCharacterDecorator機能に小さな機能を追加するためConcreteCharacterのものです。

たとえば、キャラクターが敵に与えるダメージを表す値をmethodA()返しますintConcreteCharacterDecorator単純にこの値に追加されます。したがって、必要なのはコードをに追加することだけmethodA()です。の機能はmethodB()変わりません。

ConcreteCharacterDecorator このようになります:

class ConcreteCharacterDecorator extends AbstractCharacterDecorator{

    ConcreteCharacter character;

    public ConcreteCharacterDecorator(ConcreteCharacter character){
        this.character = character;
    }

    public int methodA(){
        return 10 + character.methodA();
    }

    public int methodB(){
        character.methodB(); // simply delegate to the wrapped object.
    }

}

これは、2つのメソッドを含む小さなクラスでは問題ありません。

しかし、AbstractCharacter15のメソッドを定義するとどうなるでしょうか。 ConcreteCharacterDecorator少しの機能を追加することだけを目的としていますが、すべてを実装する必要があります。

少し機能を追加する1つのメソッドと、単純に内部オブジェクトに委譲する別の14のメソッドを含むクラスになります。

次のようになります。

class ConcreteCharacterDecorator extends AbstractCharacterDecorator{

    ConcreteCharacter character;

    public ConcreteCharacterDecorator(ConcreteCharacter character){
        this.character = character;
    }

    public int methodA(){
        return 10 + character.methodA();
    }

    public int methodB(){
        character.methodB(); // simply delegate to the wrapped object.
    }
    public int methodC(){
        character.methodC(); // simply delegate to the wrapped object.
    }
    public int methodD(){
        character.methodD(); // simply delegate to the wrapped object.
    }
    public int methodE(){
        character.methodE(); // simply delegate to the wrapped object.
    }
    public int methodF(){
        character.methodF(); // simply delegate to the wrapped object.
    }
    public int methodG(){
        character.methodG(); // simply delegate to the wrapped object.
    }
    public int methodH(){
        character.methodH(); // simply delegate to the wrapped object.
    }
    public int methodI(){
        character.methodI(); // simply delegate to the wrapped object.
    }
    public int methodJ(){
        character.methodJ(); // simply delegate to the wrapped object.
    }
    public int methodK(){
        character.methodK(); // simply delegate to the wrapped object.
    }
    public int methodL(){
        character.methodL(); // simply delegate to the wrapped object.
    }
    public int methodM(){
        character.methodM(); // simply delegate to the wrapped object.
    }
    public int methodN(){
        character.methodN(); // simply delegate to the wrapped object.
    }
    public int methodO(){
        character.methodO(); // simply delegate to the wrapped object.
    }

}

明らかに、非常に醜い。

私はおそらく、デコレータでこの問題に遭遇する最初ではありません。どうすればこれを回避できますか?

回答:


6

迅速かつ簡単:AbstractCharacterDelegator抽象関数ではなく、仮想関数を備えています。デフォルトでは、それらはラップされたキャラクターに渡され、オーバーライドできます。次に、継承して、必要なものだけをオーバーライドします。

この種のものは、かなりの装飾を行い、共通のインターフェースが特に広い場合を除いて、特に有用ではありません。それはあまりにも一般的ではありません。そしてこのアプローチはデコレーターという文字ではないかもしれませんが、それは概念を使用しており、パターンを知っているプログラマーによってデコレーターであるとよく理解されます。


つまり、JavaでメソッドをAbstractCharacterDelegator「通常」で作成することをお勧めします(Javaのすべての「通常」メソッドはデフォルトで仮想であるため、サブクラスを継承することでオーバーライドできるため)。そして、それらの実装は単に内部オブジェクトに委任されています-そしてそれを変更したい場合は、単にそれらをオーバーライドしますか?
Aviv Cohn

もしそうなら、それはインターフェイスがメソッドでの実装を許可していないので、インターフェイスではなく抽象クラスを使わなければならないことを意味します。だから私はよりクリーンなコードを用意しますが、インターフェースによる「多重継承」はありません。どちらがより重要ですか?
Aviv Cohn

@Progはい、それは彼が言っていることです。インターフェースの使用を除外するものではありません。バックUMLダイアグラムに行く、あなたは変えることができますAbstractCharacterinterface。残りはほとんど同じままで、サブクラス化の代わりにインターフェースConcreteCharacterAbstractCharacterDecorator実装します。すべてがうまくいくはずです。おそらく他に何も拡張する必要はありませんがAbstractCharacterDecorator、何らかの理由で別のクラスを拡張する必要が生じた場合に、昔ながらの方法で委任を常に行うことができるため、デコレータはサブクラス化にロックされません。
ドヴァル2014年

@Dovalしたがって、基本的には、AbstractCharacterをインターフェイスにして、それ以外はすべて具象クラスまたは抽象クラスにするのが最善の方法です。これはTelastynが言ったようにクリーンなコードを可能にしますが、より柔軟性のためにすべての抽象クラスを使用します。正しい?
Aviv Cohn

1
@Progはい、それについて要約します。
Doval 2014年

3

AbstractCharacterに15のメソッドがある場合は、おそらくより小さなクラスに分割する必要があります。それはさておき、委任に対して何らかの特別な構文サポートを提供する言語がなければ、他にできることは多くありません。すべての委任が始まる場所にコメントを残すだけで、読者は彼がこれ以上見下す必要がないことがわかります。すべての転送メソッドの記述に関しては、IDEが役立ちます。

それはさておき、Decoratorはインターフェイスで最もよく機能すると私は考えています。一般に、抽象化を有限(おそらく0)のサブクラス数に制限するか、またはインターフェイスを使用してすべての方法を実行します。無制限の継承は、インターフェースの欠点のすべてを持っている(あなたが間違った実装を渡すことができます)がありますが、完全な柔軟性得ることはありません(あなたが唯一のサブクラス一つのことができます。)さらに悪いこと、それはあなたを開き脆弱基本クラスの問題と、それの構成ほど柔軟ではありません(SubclassAとSubclassBを作成しますが、両方から継承して両方の機能を持つSubclassABを作成することはできません)。そのため、装飾をクラッジにすることをサポートするためだけに、継承ツリーに強制されます。


+1は、凝集性の問題(15のメソッドは責任が多すぎる)を示し、インターフェースを使用するためのものです。
Fuhrmanator 2014年

時には、それを小さなクラスに分解できない場合があります。たとえば、ファイルシステムクラスです。
Vicente Bolea
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.