広告

Java : Factory Method パターン (図解/デザインパターン)

Factory Method パターンとは、GoF によって定義されたデザインパターンの1つです。
ベースとなるクラスで共通の処理を実装して、その処理で使うオブジェクトの生成処理はサブクラスに任せる、という設計です。

本記事では、Factory Method パターンを Java のコード付きで解説していきます。


デザインパターン(GoF) 関連記事

概要

Factory Method パターン(ファクトリメソッド・パターン)[1]とは、GoF (Gang of Four; 四人組)によって定義されたデザインパターンの1つである。 Factory Method パターンは、他のクラスのコンストラクタをサブクラスで上書き可能な自分のメソッドに置き換えることで、 アプリケーションに特化したオブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高めることを目的とする。

Factory Method パターンとは、

  • 処理はベースクラスで共通化
  • 部品となるオブジェクトの生成処理はサブクラスで実装

という設計です。

Factory Method は、日本語的に発音すると「ファクトリ・メソッド」となります。
意味は「工場メソッド」ですね。


【Factory Method パターンのイメージ】

イメージ図1

ベースクラスで処理を共通化して、サブクラスでそれぞれ 異なる 部品を生成します。

イメージ図2

サブクラスを

  • サブクラスA → サブクラスB

に切り替えることで、共通処理で使う 部品を切り替える ことができます。
そのさい、ベースクラスは 修正なし で OK です。

もし「部品C」の処理が必要になったら、サブクラスC を作るだけです。
もちろん、ベースクラスの修正は必要ありません。

これが Factory Method パターンのメリットになります。


【補足】

Factory Method パターン は Template Method パターン の応用です。
そちらも確認すると、より理解を深められるかもしれません。

あとは、Factory Method パターンの代わりに Abstract Factory パターン が使えないか検討してみるのもよいでしょう。

Factory Method パターンは 継承(extends) によって問題を解決します。
一方、Abstract Factory パターンは 委譲 によって問題を解決します。

一般的に、継承よりも委譲のほうが依存度は弱くなります。
依存度が弱いと、

  • 機能の拡張がしやすい
  • ユニットテストしやすい

などのメリットがあります。

関連記事:


クラス図とシーケンス図

クラス図1

シーケンス図1

上の図は、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 を考えてみます。

UI図1

とはいえ、GUI をコード例にするのは複雑になるので、ここでは単純なテキスト出力のみで考えます。
(あくまで上記のダイアログはイメージということで…)

ノート

  • Abstract Factory パターン の記事でも似たケースのコード例を解説しています。
    比較してみるのも面白いかもしれません。

ダイアログは、UI の部品として

  • ラベル  : 「処理を実行しますか?」
  • ボタン1 : 「OK」
  • ボタン2 : 「CANCEL」

を持ちます。

クラス図2

さて、このダイアログですが、Windows と Mac の両方で使いたいとしましょう。
コードもなるべく使いまわせるようにしたいです。

ただ、ラベルとボタンは、どうしても Windows 用と Mac 用で分ける必要がでてきました。
上のクラス図でいうと、色のついたクラスが Windows 用と Mac 用のクラスです。

それでは Factory Method パターン を使ってコードを書いてみましょう。

クラス図3


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 パターン を使ったほうがよいケースもあるかもしれません。

よく検討してから使いたいですね。


関連記事

ページの先頭へ