はじめに
本投稿はJava言語で学ぶデザインパターン入門のデザインパターンをまとめた記事です。今回はSingletonパターンになります。
まとめ一覧はこちら
Template Methodパターン
Templateとは
Wikipedia参照
文書などのコンピュータデータを作成する上で雛形となるデータ
とありますが、本書では文字を綺麗に書くためのテンプレート板を例に紹介されています。
異なる色のペンや、筆・クレヨンなどを用いても同じ形の文字になるように、スーパークラスで処理の枠組みを決め、サブクラスで具体的な内容を定めるデザインパターンをTemplate Methodパターンと呼びます。
手続き型プログラミング言語は似た処理を一つの関数にまとめるのに対し、オブジェクト指向言語では、自然と似た処理の部分を1つのクラスとしてまとめていると思います。この設計はまさにTemplate Methodパターンに準拠していますね。
似たような流れの処理を共通化したい時にこのパターンを適応することが多いです。
Template Methodパターンのクラス図
- AbstractClassで抽象メソッドの宣言、テンプレートメソッドの実装
- ConcreteClassで抽象メソッドの実装
具体例
文字・文字列を3回繰り返し表示する簡単な例で紹介していきます
- AbstractClass :AbstractDisplayクラス
- ConcreteClass
- CharDisplayクラス(文字表示)
- StringDisplayクラス(文字列表示)
Mainクラス
クラスの生成と一緒に渡した文字(列)が、加工されて表示されるようなクラスを作っていきます。
public static void main(String[] args) {
//※
AbstractDisplay cd = new CharDisplay('T');
AbstractDisplay sd = new StringDisplay("Design Pattern");
AbstractDisplay sd2 = new StringDisplay("Template Method");
cd.display();
sd.display();
sd2.display();
}
異なるサブクラスの生成をスーパークラスに代入しています。
詳しくはLSP参照(後述)
AbstractClass
文字・文字列を3回繰り返し表示するTemplate Methodを実装しています
public abstract class AbstractDisplay {
public abstract void open();
public abstract void print();
public abstract void close();
//Template Method
public final void display() {
open();
for (int i =0; i < 3; i++) {
print();
}
close();
}
}
ConcreteClass
- 文字の前後に***を付与
public class CharDisplay extends AbstractDisplay {
private char ch;
public CharDisplay(char ch) {
this.ch = ch;
}
public void open() {
System.out.print("***");
}
public void print() {
System.out.print(ch);
}
public void close() {
System.out.println("***");
}
}
- 文字列の周りを枠線で囲む
public class StringDisplay extends AbstractDisplay {
private String string;
private int width;
public StringDisplay(String string) {
this.string = string;
this.width = string.getBytes().length;
}
public void open() {
printLine();
}
public void print() {
System.out.println("|" + string + "|");
}
public void close() {
printLine();
}
private void printLine() {
System.out.print("+");
for (int i = 0; i < width; i++) {
System.out.print("-");
}
System.out.println("+");
}
}
実行結果
***TTT***
+--------------+
|Design Pattern|
|Design Pattern|
|Design Pattern|
+--------------+
+---------------+
|Template Method|
|Template Method|
|Template Method|
+---------------+
このようにスーパクラスのTemplateMethodのロジックで、処理の共通化ができてますね。
追記
-
メリット
-
スーパークラスにロジックが集中しているので、バグが生じてもTemplateMethodのみを修正すればいい
-
サブクラスの設計が簡潔になる
-
デメリット
-
サブクラスの数が増加する
-
親と子の関係が密接なので、スーパークラスのソースを確認しないと実装が難しい
-
スーパークラスの処理が大きくなると、サブクラスの自由度が減少する
LSP(The Liskov Substitution Principle)
オブジェクト指向プログラミングにおける派生型の定義の一種で、リスコフの置換原則ともいわれます。
簡単に言うとTemplate Methodのように、スーパークラスとサブクラスを定義するとき、サブクラスはスーパークラスを置き換えることができなければならないという原則です。
Aクラスに依存するBクラスが存在するとする。
BクラスをのサブクラスB', やさらにサブクラスB''に置き換えても正常の動作しなければならないということです。
例えば...
- サブクラスでオーバーライドされた結果、動作が意図しないものに変わってはならない
- ・公開する必要がないメンバのアクセス修飾子は、privateにする
- ・オーバーライドをする必要がなければ、final修飾子のような宣言をする(Java)
- 結果がスーパークラスの意図から外れてしまうようなオーバーライドはしない
- クラスAはサブクラスB'やB"を判定して処理をするような設計であってはならない
まとめると
- 同じスーパークラス(インタフェース)を持っていて、違うオブジェクトでも同じように扱える
- オブジェクトによって動作は異なる
- クライアントからは同じ動作をしているように見えなければならない
の条件を守った設計であれば、継承関係でバグが発生しなくうまくいくということなんですね。
参考
サンプルコードについて
以下のレポジトリにソースコードをアップしてあります。
shoheiyokoyama/design-pattern_java
デザインパターン
- 生成に関するパターン
- Abstract factory
- Builder
- Factory Method
- Prototype
- Singleton
- 構造に関するパターン
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- 振る舞いに関するパターン
- Chain of responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor