【Java】Decoratorパターン【デザインパターン】
目次
Decoratorパターンについて
Decoratorパターンとは
- Decoratorパターンは、オブジェクトに対してデコレーション (飾り付け) を行うパターンです
- スポンジに苺、チョコ、生クリーム。。とデコレーションするように、オブジェクトも機能を一つ一つ被せてデコレーションしていくイメージです
サンプルプログラム
- Decoratorパターンを使って、文字列の周りに飾り枠をつけて表示する例を取り上げます
- 各クラスの役割は以下のようになっています
名前 | 役割 |
---|---|
Display | 文字列表示用の抽象クラス |
StringDisplay | 1行だけの文字列表示用のクラス |
Border | 飾り枠を表す抽象クラス |
SideBorder | 左右に飾り枠をつけるクラス |
FullBorder | 上下左右に飾り枠をつけるクラス |
Displayクラス
- 複数行の文字列を表示するための抽象クラスです
- showメソッドでは、getRowsメソッドで行数を取得し、getRowTextメソッドで表示する文字列を取得し、forループを使って全行を表示します
- showはgetRows、getRowTextメソッドの抽象メソッドを使ったTemplate Methodパターンになっています
public abstract class Display { public abstract int getColumns(); // 横の文字数を得る public abstract int getRows(); // 縦の行数を得る public abstract String getRowText(int row); // row番目の文字列を得る public void show() { // 全部表示する for (int i = 0; i < getRows(); i++) { System.out.println(getRowText(i)); } } }
StringDisplayクラス
- StringDisplayクラスは、一行の文字列を表示するクラスです
- StringDisplayクラスはDisplayクラスのサブクラスなので、Displayクラスで宣言している抽象メソッドを実装します
- stringフィールドに表示する文字列を保持します
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() { // 行数は1 return 1; } public String getRowText(int row) { // rowが0のときのみstringを返す if (row == 0) { return string; } else { return null; } } }
Borderクラス
- 飾り枠を表すクラスです
- Displayクラスを継承しているので、飾り枠は中身と同じメソッドを持つことになります
- インターフェース的には、飾り枠と中身を同一視できることになります
- "中身"はSrtingDisplayクラスなのか、Borderクラスのサブクラスのインスタンス(飾り枠)かは分かりません
- Borderクラスのサブクラスのインスタンスも、さらにdisplayフィールドを持ちます
public abstract class Border extends Display { protected Display display; // この飾り枠がくるんでいる"中身"を指す protected Border(Display display) { // インスタンス生成時に"中身"を引数で指定 this.display = display; } }
SideBorderクラス
- Borderクラスのサブクラスの一つで、具体的な飾り枠です
- SideBorderクラスはスーパークラスで宣言したメソッドを全て実装しているので、抽象クラスではありません
public class SideBorder extends Border { private char borderChar; // 飾りとなる文字 public SideBorder(Display display, char ch) { // コンストラクタでDisplayと飾り文字を指定 super(display); this.borderChar = ch; } public int getColumns() { // 中身の両側+飾り文字分を加えた return 1 + display.getColumns() + 1; } public int getRows() { // 行数は中身と同じ return display.getRows(); } public String getRowText(int row) { // 中身の指定行の両側+飾り文字 return borderChar + display.getRowText(row) + borderChar; } }
FullBorderクラス
- Borderクラスのサブクラスの一つで、具体的な飾り枠です
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) { // 文字chをcount個連続させた文字列を作る StringBuffer buf = new StringBuffer(); for (int i = 0; i < count; i++) { buf.append(ch); } return buf.toString(); } }
Mainクラスで動作確認
public class Main { public static void main(String[] args) { Display b1 = new StringDisplay("Hello, world."); //飾りなし Display b2 = new SideBorder(b1, '#'); //#の文字で左右に飾り付け Display b3 = new FullBorder(b2); //全体の飾り枠をつけた b1.show(); b2.show(); b3.show(); Display b4 = //何重にも重ねて枠をつける new SideBorder( new FullBorder( new FullBorder( new SideBorder( new FullBorder( new StringDisplay("こんにちは。") ), '*' ) ) ), '/' ); b4.show(); } }
Decoratorパターンのメリット
Decoratorパターンでは、飾り枠(Border)も中身(StringDisplay)も共通のインターフェースを持っています。 インターフェースは共通ですが、包めば包むほど機能が追加されていきます。 その際に、包まれる方を修正する必要はありません。 包まれるものを変更することなく、機能の追加を行うことができます。
今日のポイント
- Decoratorパターンは、オブジェクトに対してデコレーション (飾り付け) を行うパターンです
本日もお疲れ様です😊