Design Pattern | Decorator Pattern(デコレーターパターン)

Decoratorパターンとは?

  • Decoratorという英単語は装飾する(Decorate)という意味である。既存の内容に何かを付け加えるという意味合いが強い。
  • Decoratorパターンは、オブジェクトに続けてデコレーション(装飾)を追加する方式である。
  • ピザを例にすると、既存のピザの上にトッピングを追加すると考えればよい。
  • スポンジケーキにクリーム、チョコレート、いちごなどで装飾できるように、オブジェクトにも機能を1つずつかぶせて装飾する方式である。
  • GoFデザインパターンでは、構造に関するデザインパターンに分類される。

Decoratorパターンのサンプルプログラム

入力した文字列に対して、枠線などの装飾を行うサンプルプログラムである。

Class Diagram
Decorator Pattern Class Diagram

1. Displayクラス

文字列行表示用の抽象クラスである。

Display.java

package com.devkuma.designpattern.structural.decorator;

public abstract class Display {

    // 列の文字数を返す。
    public abstract int getColumns();

    // 行数を返す。
    public abstract int getRows();

    // 指定された行の文字列を返す。
    public abstract String getRowText(int row);

    public void show() {
        for (int i = 0; i < getRows(); i++) {
            System.out.println(getRowText(i));
        }
    }
}

2. StringDisplayクラス

1行だけで構成される文字列行表示用のクラスである。

StringDisplay.java

package com.devkuma.designpattern.structural.decorator;

public class StringDisplay extends Display {

    private String string;

    public StringDisplay(String string) {
        this.string = string;
    }

    public int getColumns() {

        return string.getBytes().length;
    }

    public int getRows() {
        return 1;
    }

    public String getRowText(int row) {
        return (row == 0) ? string : null;
    }
}

3. Borderクラス

装飾枠を表す抽象クラスである。

Border.java

package com.devkuma.designpattern.structural.decorator;

public abstract class Border extends Display {

    protected Display display;

    protected Border(Display display) {
        this.display = display;
    }
}

4. SideBorderクラス

左右に装飾枠を付けるクラスである。

SideBorder.java

package com.devkuma.designpattern.structural.decorator;

public class SideBorder extends Border {

    public SideBorder(Display display) {
        super(display);
    }

    public int getColumns() {
        // 文字数は内容の両側に装飾した文字数を加えた数。
        return 1 + display.getColumns() + 1;
    }

    public int getRows() {
        // 行数は内容の行数と同じ。
        return display.getRows();
    }

    public String getRowText(int row) {
        return "*" + display.getRowText(row) + "*";
    }
}

5. FullBorderクラス

上下左右に装飾枠を付けるクラスである。

FullBorder.java

package com.devkuma.designpattern.structural.decorator;

public class FullBorder extends Border {

    public FullBorder(Display display) {
        super(display);
    }

    public int getColumns() {
        // 文字数は内容の両側に左右の装飾文字数を加えた数。
        return 1 + display.getColumns() + 1;
    }

    public int getRows() {
        // 行数は内容の行数に上下の装飾文字数を加えた数。
        return 1 + display.getRows() + 1;
    }

    public String getRowText(int row) {
        if (row == 0) {
            // 上端の枠
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else if (row == display.getRows() + 1) {
            // 下端の枠
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else {
            // その他
            return "|" + display.getRowText(row - 1) + "|";
        }
    }

    private String makeLine(char ch, int count) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
}

6. Mainクラス

メイン処理を実行するクラスである。

Main.java

package com.devkuma.designpattern.structural.decorator;

public class Main {

    public static void main(String[] args) {
        Display display1 = new StringDisplay("Hello world");
        display1.show();
        System.out.println("");

        Display display2 = new SideBorder(display1);
        display2.show();
        System.out.println("");

        Display display3 = new FullBorder(display2);
        display3.show();
        System.out.println("");

        Display display4 =
                new FullBorder(
                        new SideBorder(
                                new FullBorder(
                                        new StringDisplay("Hello world"))));
        display4.show();
    }
}

7. 実行結果

Hello world

*Hello world*

+-------------+
|*Hello world*|
+-------------+

+---------------+
|*+-----------+*|
|*|Hello world|*|
|*+-----------+*|
+---------------+

Decoratorパターンのメリット

Decoratorパターンでは、装飾枠(Border)も中身(StringDisplay)も共通のインターフェースを持っている。インターフェースは共通だが、包めば包むほど機能が追加されていく。そのとき、包まれる側を修正する必要はない。包むものを変更せずに機能を追加できる。