【Gang of Four】デザインパターン学習 - Decorator


Decorator - 装飾者

目次
いきなり話が脇道に逸れますが、デザインパターンやオブジェクト指向に関する書籍を読んでいるとよくオブジェクトに対する責務責任といった言葉がでてきます。
これは当該オブジェクトにて出来なければならないことを指しています。
また単一責務の原則というのがありますが、これはクラスを変更する理由は1つであるべき、というものです。

例えば以下のようなクラスは修正する理由が2つあります。
- 画面遷移処理を追加したい→画面に関する変更理由
- 引き算処理を追加した→計算に関する変更理由

画面計算.kt
interface 画面計算 {
    fun 描画(): Boolean
    fun 足し算(num1: Int, num2: Int): Int
}

単一責務の原則に則ると以下のように分離すべきです。

画面.kt
interface 画面 {
    fun 描画(): Boolean
    fun 画面遷移(): Boolean
}
計算.kt
interface 計算 {
    fun 足し算(num1: Int, num2: Int): Int
    fun 引き算(num2: Int, num2: Int): Int
}

この単一責務の原則というのも、やはり変更に対して柔軟に対応するための考えだと理解しています。脇道話は以上です。

では本題、目的に責任を動的に追加するとあるので、継承を用いずにオブジェクト(インスタンス)に任意のタイミングで機能を追加していくようなパターンですね。

目的

オブジェクトに責任を動的に追加する。Decorator パターンは、サブクラス化よりも柔軟な機能拡張方法を提供する。

構成要素

・Component 責任を動的に追加できるインターフェースを定義した抽象クラス
・ConcreteComponent Componentクラスの具象クラス
・Decorator Componentクラスに責任を追加するインターフェースを定義した抽象クラス
・ConcreteDecorator Decoratorクラスの具象クラス

実装

テキストビューインスタンスを生成するタイミングで、いろいろな機能を追加できるプログラムを実装します。

Component 責任を動的に追加できるインターフェースを定義した抽象クラス

Decorator Componentクラスに責任を追加するインターフェースを定義した抽象クラス

Viewコンポーネントインターフェース

ViewComponent.kt
package decorator

interface ViewComponent {
    fun draw()
}

ConcreteComponent Componentクラスの具象クラス

テキストビュー

TextView.kt
package decorator

class TextView: ViewComponent {
    override fun draw() {
        print("【テキストビュー】")
    }
}

ConcreteDecorator Decoratorクラスの具象クラス

影をつけるデコレータ

ShadowDecorator.kt
package decorator

class ShadowDecorator(viewComponent: ViewComponent): ViewComponent {
    val viewComponent = viewComponent

    override fun draw() {
        viewComponent.draw()
        addShadow()
    }

    private fun addShadow() {
        print(":影付き")
    }
}

スクロールさせるデコレータ

ScrollDecorator.kt
package decorator

class ScrollDecorator(viewComponent: ViewComponent): ViewComponent {
    val viewComponent = viewComponent

    override fun draw() {
        viewComponent.draw()
        addScroll()
    }

    private fun addScroll() {
        print(":スクロール可能")
    }

}

部品が全部できました。使っていきましょう。

使う人

ComponentManager.kt
package decorator

class ComponentManager {
    init {
        // スクロール可能 影付き テキストビュー
        val scrollShadowTextView = ScrollDecorator(ShadowDecorator(TextView()))
        // スクロール可能 テキストビュー
        val scrollTextView = ScrollDecorator(TextView())

        // 描画
        scrollShadowTextView.draw()
        print("\n")
        scrollTextView.draw()
    }
}

出力結果

実行すると以下のようになります。

[output]
【テキストビュー】:影付き:スクロール可能
【テキストビュー】:スクロール可能