Java : Factory Method パターン (図解/デザインパターン)
Factory Method パターンとは、GoF によって定義されたデザインパターンの1つです。
ベースとなるクラスで共通の処理を実装して、その処理で使うオブジェクトの生成処理はサブクラスに任せる、という設計です。
本記事では、Factory Method パターンを Java のコード付きで解説していきます。
デザインパターン(GoF) 関連記事
- 生成に関するパターン
- 振る舞いに関するパターン
概要
Factory Method パターンとは、
- 処理はベースクラスで共通化
- 部品となるオブジェクトの生成処理はサブクラスで実装
という設計です。
Factory Method は、日本語的に発音すると「ファクトリ・メソッド」となります。
意味は「工場メソッド」ですね。
【Factory Method パターンのイメージ】
ベースクラスで処理を共通化して、サブクラスでそれぞれ 異なる 部品を生成します。
サブクラスを
- サブクラスA → サブクラスB
に切り替えることで、共通処理で使う 部品を切り替える ことができます。
そのさい、ベースクラスは 修正なし で OK です。
もし「部品C」の処理が必要になったら、サブクラスC を作るだけです。
もちろん、ベースクラスの修正は必要ありません。
これが Factory Method パターンのメリットになります。
【補足】
Factory Method パターン は Template Method パターン の応用です。
そちらも確認すると、より理解を深められるかもしれません。
あとは、Factory Method パターンの代わりに Abstract Factory パターン が使えないか検討してみるのもよいでしょう。
Factory Method パターンは 継承(extends) によって問題を解決します。
一方、Abstract Factory パターンは 委譲 によって問題を解決します。
一般的に、継承よりも委譲のほうが依存度は弱くなります。
依存度が弱いと、
- 機能の拡張がしやすい
- ユニットテストしやすい
などのメリットがあります。
関連記事:
クラス図とシーケンス図
上の図は、Factory Method パターンの一般的なクラス図とシーケンス図です。
それでは、このクラス図をそのままコードにしてみましょう。
まずは、クラス図の右側の Product インタフェースとその実装クラスです。
public interface Product {
}
public class ConcreteProductA implements Product {
@Override
public String toString() {
return "Product A!";
}
}
public class ConcreteProductB implements Product {
@Override
public String toString() {
return "Product B!";
}
}
次に、Creator クラスです。
- operation メソッド : 共通処理
- createProduct 抽象メソッド : Product 生成
public abstract class Creator {
public void operation() {
System.out.println("-- operation --");
final var product = createProduct();
System.out.println("create : " + product);
}
protected abstract Product createProduct();
}
最後に、ConcreteCreator クラスです。
createProduct メソッドを実装。ConcreteProduct クラスを new して返すだけです。
public class ConcreteCreatorA extends Creator {
@Override
protected Product createProduct() {
return new ConcreteProductA();
}
}
public class ConcreteCreatorB extends Creator {
@Override
protected Product createProduct() {
return new ConcreteProductB();
}
}
それでは、これらのクラスを使ってみましょう。
final var creatorA = new ConcreteCreatorA();
creatorA.operation();
// 結果
// ↓
//-- operation --
//create : Product A!
final var creatorB = new ConcreteCreatorB();
creatorB.operation();
// 結果
// ↓
//-- operation --
//create : Product B!
結果も問題なしですね。
具体的な例
もう少し具体的な例も見てみましょう。
次のような GUI を考えてみます。
とはいえ、GUI をコード例にするのは複雑になるので、ここでは単純なテキスト出力のみで考えます。
(あくまで上記のダイアログはイメージということで…)
ノート
- Abstract Factory パターン の記事でも似たケースのコード例を解説しています。
比較してみるのも面白いかもしれません。
ダイアログは、UI の部品として
- ラベル : 「処理を実行しますか?」
- ボタン1 : 「OK」
- ボタン2 : 「CANCEL」
を持ちます。
さて、このダイアログですが、Windows と Mac の両方で使いたいとしましょう。
コードもなるべく使いまわせるようにしたいです。
ただ、ラベルとボタンは、どうしても Windows 用と Mac 用で分ける必要がでてきました。
上のクラス図でいうと、色のついたクラスが Windows 用と Mac 用のクラスです。
それでは Factory Method パターン を使ってコードを書いてみましょう。
Label と Button インタフェースは getText メソッドで文字列を返すだけです。
public interface Label {
String getText();
}
public interface Button {
String getText();
}
本来なら、Button インタフェースにはボタンを押したときの action メソッドがあったほうがよいでしょう。
ただ、今回はコード例を簡単にするために省きます。
public abstract class Dialog {
private final Label label;
private final Button button1;
private final Button button2;
public Dialog() {
this.label = createLabel("処理を実行しますか?");
this.button1 = createButton("OK");
this.button2 = createButton("CANCEL");
}
public void show() {
System.out.println("---- ダイアログ ----");
System.out.println("ラベル : " + label.getText());
System.out.println("ボタン1 : " + button1.getText());
System.out.println("ボタン2 : " + button2.getText());
}
protected abstract Label createLabel(String text);
protected abstract Button createButton(String text);
}
Dialog クラスは、
- ラベル (Label)
- ボタン1 (Button)
- ボタン2 (Button)
を持ちます。
そして、show メソッドでラベルとボタンのテキストを表示します。
抽象メソッドである createLabel, createButton メソッドでは UI部品(Label, Button) を生成します。
Factory Method パターンの大事なポイントですね。
public class WinDialog extends Dialog {
@Override
protected Label createLabel(String text) {
return new WinLabel(text);
}
@Override
protected Button createButton(String text) {
return new WinButton(text);
}
}
public class MacDialog extends Dialog {
@Override
protected Label createLabel(String text) {
return new MacLabel(text);
}
@Override
protected Button createButton(String text) {
return new MacButton(text);
}
}
Dialog のサブクラスでは、createLabel, createButton メソッドを実装します。
それぞれ、Windows 用と Mac 用の UI部品を new して返すだけです。
次は、Label インタフェースの実装クラスです。
public class WinLabel implements Label {
private final String text;
public WinLabel(String text) {
this.text = text;
}
@Override
public String getText() {
return text + " (Win)";
}
}
public class MacLabel implements Label {
private final String text;
public MacLabel(String text) {
this.text = text;
}
@Override
public String getText() {
return text + " (Mac)";
}
}
Windows 用と Mac 用にそれぞれ実装します。
同じく、Button インタフェースの実装クラスです。
public class WinButton implements Button {
private final String text;
public WinButton(String text) {
this.text = text;
}
@Override
public String getText() {
return text + " (Win)";
}
}
public class MacButton implements Button {
private final String text;
public MacButton(String text) {
this.text = text;
}
@Override
public String getText() {
return text + " (Mac)";
}
}
こちらも Windows 用と Mac 用にそれぞれ実装します。
それでは、これらを使ってダイアログを表示してみましょう。
// -----------------
// Windows 用
final var winDialog = new WinDialog();
winDialog.show();
// 結果
// ↓
//---- ダイアログ ----
//ラベル : 処理を実行しますか? (Win)
//ボタン1 : OK (Win)
//ボタン2 : CANCEL (Win)
// -----------------
// Mac 用
final var macDialog = new MacDialog();
macDialog.show();
// 結果
// ↓
//---- ダイアログ ----
//ラベル : 処理を実行しますか? (Mac)
//ボタン1 : OK (Mac)
//ボタン2 : CANCEL (Mac)
結果も問題なしですね。
Windows 用と Mac 用、それぞれ表示できています。
もし Linux 用のダイアログが必要になったら、Linux 用の UI部品と LinuxDialog クラスを作れば OK です。
ベースとなる Dialog クラスは 修正なし で使いまわせます。
まとめ
Factory Method パターンとは、
- 処理はベースクラスで共通化
- 部品となるオブジェクトの生成処理はサブクラスで実装
という設計です。
もしかしたら、代わりに Abstract Factory パターン を使ったほうがよいケースもあるかもしれません。
よく検討してから使いたいですね。
関連記事
- 標準APIにならう命名規則
- コメントが少なくて済むコードを目指そう
- シングルトン・パターンの乱用はやめよう
- メソッドのパラメータ(引数)は使う側でチェックしよう
- 不変オブジェクト(イミュータブル) とは
- 依存性の注入(DI)をもっと気軽に
- 不要になったコードはコメントアウトで残さずに削除しよう
- 簡易的な Builder パターン
- 読み取り専用(const) のインタフェースを作る
- 図解/デザインパターン一覧 (GoF)
- Abstract Factory パターン
- Adapter パターン
- Bridge パターン
- Builder パターン
- Chain of Responsibility パターン
- Command パターン
- Composite パターン
- Decorator パターン
- Facade パターン
- Flyweight パターン
- Interpreter パターン
- Iterator パターン
- Mediator パターン
- Memento パターン
- Observer パターン
- Prototype パターン
- Proxy パターン
- Singleton パターン
- State パターン
- Strategy パターン
- Template Method パターン
- Visitor パターン