【オブジェクト指向のこころ】17章 Decoratorパターン

デザインパターンとともに学ぶオブジェクト指向のこころ」の学習記録。
「第17章 Decoratorパターン」のまとめ。

Decoratorパターンの目的

GoFによれば、Decoratorパターンの目的は以下の通り。

  • オブジェクトに対して新たな責務を動的に付与する。Decoratorによってサブクラス化による機能拡張よりも柔軟な代替策が実現できる。

SalesTicket(売上伝票)にヘッダ、フッタ情報を追加する場合を考える。

元の設計はSalesOrder(注文)がSalesTicketを使用する設計になっている。

http://www.plantuml.com/plantuml/png/LSun3i8m38NXtQVm2492d02LaOcXN21DdaJKE4IsEm3YxXHOsj6_FaklCm_ganIoCTSMD0Z74inExfg7oN6Ezzhd9_X0jvp0aEOF5iW0DQ8VvZuKs4KJb5_qNjLhZYDypZjkwlzhi_sYnTF-kCJKeQPvqmS0

最も簡単な解決策は、SalesTicketクラスでヘッダやフッタの制御を行うことである。
例えばSalesTicketにヘッダやフッタの印刷有無を指示するフラグを渡し、制御を追加する。

http://www.plantuml.com/plantuml/png/POqnhi9034HxdyBb_qAAE04A3OAA8bnWarXYbNKIsTu247SdYeiiefnvzhnQBOXbJg137ESWWR5BGVLe9h7DHfTk6flu9Dp3FVQ4nBCza8bR4WLOZx49YVGgFGc-uFtLtc8ta2tr3cSnJt__sz6PWh-jt5F9JzDa6_Va70fhTIXjK9ghXVdiJ-rhKDFOPquV

選択肢があまり多くない場合や、機能に変更が発生しない場合にはこれでもよい。

しかし次のケースを考えるとこの設計には無理がある。

  • 1枚の売上伝票上にヘッダやフッタを複数枚印刷する場合
  • 複数のヘッダやフッタの順序を変更する場合

Decoratorパターンはこういった場合に威力を発揮する。
Decoratorパターンは何らかの制御メソッドを使って追加機能を制御するのではなく、必要な機能を必要な順で連結することが可能になる。

パターンの適用

Decoratorパターンのクラス図は以下の通り。 http://www.plantuml.com/plantuml/png/ZOynRiCm34LtdeBmr0u9qhr3x7Rfd1FG4e48jKI1eWkqRkvU5qOgG1cQYGFV-_Z_M8pKvZX1UGyfar94Dr4flPc6SJP-Sv9EuOln0EVW6Jbc-yJ8SMGj03GMKtAsMVWYCKlYP8WBBDVd1IMpaWL93uzm0RZ2oIaRLsc11cpYNzc9aecsP1lv1_OhITwpFnaPtoaDJNp_6p3mcJw2w6RMpi_Vkztjb0QepQ5kw0xx_LE7ihwxzLQtCfjyk-fVOe0Z9p_7wGS0

今回の事例では、ConcreteComponentはSalesTicket、ConcreteDecoratorはHeader、Footerとなる。

http://www.plantuml.com/plantuml/png/ZP1DJiCm48NtSufHLY2KgCqI8LG4Gh0k5t2SaR3gdyYUB4hftObn89K3RLQM_NqpJtwRmC9pRpI4dRATyCAWr2A41sQlcfxfLJgxLH-4AtXNBI6PZlTeo3Ja0y2tZ8zQaMKym150D86za8mZSgPpDj81t-3X1n-ny_ocv8xuwZhE9NqhD8Nq7FrSofOdz4JIUS7E3nCbclrlvEsKlY3wXKHBVZsBBUCjmRbUBzMVdUD_joSur-iBUlHXBB-wF_ajJBNVVPRbQQqvoZg4lDDYLLNh0jsW5iCvPUHcMZUsU0xMVyEneNEmXWtPjZVw2m00

コード例

abstract public class Component {
    abstract public void prtTicket();
}

public class SalesTicket extends Component {
    @Override
    public void prtTicket() {
        //売上伝票の印刷処理
    }
}

abstract public class TicketDecorator extends Component {
    private Component myComponent;

    public TicketDecorator(Component myComponent) {
        this.myComponent = myComponent;
    }

    public void callTrailer() {
        if (myComponent != null) {
            myComponent.prtTicket();
        }
    }
}

public class Header1 extends TicketDecorator {
    public Header1(Component myComponent) {
        super(myComponent);
    }

    @Override
    public void prtTicket() {
        // ヘッダ1用のコード
        callTrailer();
    }
}

例えば以下のような売上伝票を印刷する場合、

ヘッダ1
売上伝票
フッタ1

次のように実行する。

new Header1(new Footer1(new SalesTicket()));

Header1オブジェクトの後にFooter1オブジェクト、そしてその後にSalesTicketオブジェクトが連結される。

まとめ

  • Decoratorパターンによって、Decoratorの実装を、その使用方法を決定するオブジェクトから切り離すことができる。
  • また各Decoratorは、付加する機能に対してのみ責任を持ち、付加方法を管理する必要がなくなるため、凝縮度も高まる。
  • コードを変更することなしに、付加機能の実行順序を変更できるようにもなる。