デザインパターン ~Decorator~


1. はじめに

GoFのデザインパターンにおける、Decoratorパターンについてまとめます。

2. Decoratorパターンとは

  • Decoratorという英単語は、飾り付け(Decorate)するものという意味になります。
  • Decoratorパターンは、オブジェクトに対してどんどんデコレーション(飾り付け)を行う方式です。
  • スポンジケーキに対し、クリーム、チョコレート、イチゴ・・・等とデコレーションできるように、オブジェクトも機能を一つ一つ被せてデコレーションしていくイメージになります。
  • GoFのデザインパターンでは、生成に関するデザインパターンに分類されます。

3. サンプルクラス図

4. サンプルプログラム

入力した文字列に対して、枠づけ等の装飾を行うプログラムです。

4-1. Displayクラス

文字列表示用の抽象クラスです。

Display.java
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));
        }
    }
}

4-2. StringDisplayクラス

1行だけからなる文字列表示用のクラスです。

StringDisplay.java
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;
    }
}

4-3. Borderクラス

飾り枠を表す抽象クラスです。

Border.java
public abstract class Border extends Display {

    protected Display display;

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

4-4. SideBorderクラス

左右に飾り枠をつけるクラスです。

SideBorder.java
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) + "*";
    }
}

4-5. FullBorderクラス

上下左右に飾り枠をつけるクラスです。

FullBorder.java
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();
    }
}

4-6. Mainクラス

メイン処理を行うクラスです。

Main.java
public class Main {

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

        Display b2 = new SideBorder(b1);
        b2.show();
        System.out.println("");

        Display b3 = new FullBorder(b2);
        b3.show();
        System.out.println("");

        Display b4 =
            new FullBorder(
                new SideBorder(
                    new FullBorder(
                        new StringDisplay("Hello japan"))));
        b4.show();
    }
}

4-7. 実行結果

Hello world

*Hello world*

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

+---------------+
|*+-----------+*|
|*|Hello japan|*|
|*+-----------+*|
+---------------+

5. メリット

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

6. GitHub

7. デザインパターン一覧

8. 参考

今回の記事、及びサンプルプログラムは、以下の書籍を元に作成させて頂きました。

大変分かりやすく、勉強になりました。感謝申し上げます。
デザインパターンやサンプルプログラムについての説明が詳細に書かれていますので、是非書籍の方もご覧ください。