デザインパターンabstract factoryについて勉強した

abstract factoryパターンとは

訳すと抽象的な工場です。
抽象クラスのコンストラクタを作り、その抽象的なコンストラクタからメソッドを呼び出す(ように見える)コードパターンです。
実際には抽象クラスを継承した具象クラスは存在しますが、表面的には見えにくいようにコードを書きます。

コード例

ジュース工場を例にします。
何を作るかはまだ決まっていない「抽象ジュース工場」、なんの味かはまだ決まっていない「抽象ジュース」が登場します。

JuiceFactoryクラス

public abstract class JuiceFactory {

	private static AppleJuiceFactory apple = new AppleJuiceFactory();
	private static OrangeJuiceFactory orange = new OrangeJuiceFactory();
	private static BananaJuiceFactory banana = new BananaJuiceFactory();

	public static JuiceFactory createFactory(String name) {
		switch(name) {
		case "りんご":
			return apple;
		case "オレンジ":
			return orange;
		case "バナナ":
			return banana;
		}
		return null;
	}

	public abstract Juice buy() ;

	public abstract void saleCount() ;
}

抽象ジュース工場です。

3つのフィールドは具象ジュース工場の候補です。

createFactoryメソッドでは渡された文字列からジュース工場を選択して返します。
これがコンストラクタの代わりとなります。
つまり、「りんご」という文字列を渡すことでAppleJuiceFactoryクラス(後述)のインスタンスが得られるわけです。
何が嬉しいのかはもう少し後で説明します。

buyメソッドはこの工場でジュースを購入するメソッドです。
サブクラスで実装します。

saleCountメソッドはこの工場で購入されたジュースの数をコンソールに出力します。
サブクラスで実装します。

Juiceインタフェース

public interface Juice {

	void taste();
}

抽象ジュースです。

tasteメソッドはジュースの味をコンソール出力します。
サブクラスで実装します。


以上が抽象ジュース工場と抽象ジュースです。
この工場を使用する側が気にするのはここまでです。

実装

ここからは具象ジュース工場と具象ジュースを実装していきます。

AppleJuiceFactoryクラス

public class AppleJuiceFactory extends JuiceFactory {

	private int count = 0;

	@Override
	public Juice buy() {
		count++;
		return new AppleJuice();
	}

	@Override
	public void saleCount() {
		System.out.println(String.format("うちのリンゴジュースは%d個売れています",count));
	}
}

リンゴジュース工場です。
countフィールドは今まで売れたジュースの数を保持します。

buyメソッドはこの工場からジュースを購入するメソッドでした。
ジュースの販売数を1増やしてAppleJuiceを返しています。

saleCountメソッドは売れたジュースの数をコンソール出力します。

AppleJuiceクラス

public class AppleJuice implements Juice {

	@Override
	public void taste() {
		System.out.println("みずみずしいリンゴジュース");
	}
}

リンゴジュースです。
tasteメソッドはジュースの味をコンソール出力します。

以降のオレンジジュース、バナナジュースもリンゴジュースと同じように実装していきます。

OrangeJuiceFactoryクラス

public class OrangeJuiceFactory extends JuiceFactory {

	private int count = 0;

	@Override
	public Juice buy() {
		count++;
		return new OrangeJuice();
	}

	@Override
	public void saleCount() {
		System.out.println(String.format("うちのオレンジジュースは%d個売れています",count));
	}

}

オレンジジュース工場

OrangeJuiceクラス

public class OrangeJuice implements Juice {

	@Override
	public void taste() {
		System.out.println("甘酸っぱいオレンジジュース");
	}
}

オレンジジュース

BananaJuiceFactoryクラス

public class BananaJuiceFactory extends JuiceFactory {

	private int count = 0;

	@Override
	public Juice buy() {
		count++;
		return new BananaJuice();
	}

	@Override
	public void saleCount() {
		System.out.println(String.format("うちのバナナジュースは%d個売れています",count));
	}
}

バナナジュース工場

BananaJuiceクラス

public class BananaJuice implements Juice {

	@Override
	public void taste() {
		System.out.println("バナナジュース");
	}
}

バナナジュース

実行

public class Main {

	public Main() {
		JuiceFactory factory ;
		Scanner scanner = new Scanner(System.in);
		String text ;

		while(true) {
			System.out.println("********");
			System.out.print("ジュースを選んでください(りんご,オレンジ,バナナ):");
			text = scanner.nextLine();
			if(text.equals("exit")) break;
			factory = JuiceFactory.createFactory(text);
			if(factory==null) continue;
			factory.saleCount();

			while(true) {
				System.out.print("ジュースを何個書いますか:");
				text = scanner.nextLine();
				if(!text.matches("\\d{1,3}")) continue;
				break;
			}
			for(int i=Integer.parseInt(text);i>0;i--) {
				factory.buy().taste();
			}
		}
		scanner.close();
	}

	public static void main(String[] args) {
		new Main();
	}
}

実行すると、コンソールにりんご、オレンジ、バナナのいずれかを入力するように求められます。
もしくはexitを入力するとプログラムを終了します。
new Scanner(System.in)はコンソールからの入力を要求するためのインスタンスを生成しています。
このインスタンスのnextLineメソッドを呼ぶことで、コンソールが入力待ち状態になります。

りんご、オレンジ、バナナのいずれかを正しく入力しないと、JuiceFactory.createFactoryメソッドは該当するクラス(ジュース工場)が存在しないということでnullを返します。

工場を選択すると、工場のオーナーが現在の売れ行きを教えてくれます。

2段目のwhileではジュースの購入数を入力します。
購入する数を入力するとその分だけジュースを飲むことができます。

ちなみに、if(!text.matches("\\d{1,3}")) continue;の部分は、コンソールから入力された文字列が全て半角数字で入力されているかをチェックする正規表現です。正しくなければループの先頭に戻され、もう一度購入数を聞かれます。

特徴

例の通り、ユーザからの入力によってクラスを選択するような場面で使用されます。
外部からの文字列入力によって使用するクラスが決定できるので、クラスを選択するという点では
JuiceFactoryクラスのサブクラスを作ってもcreateFactoryメソッドを書き換えなければこのパターンとしては使えません。
この例ではcreateFactoryメソッド内でnew AppleJuiceFactoryのようにnew をしていましたが、他にもClass.getConstructorメソッドを使った方法もあるようです。

補足

今回は日本語入力のみに対応するプログラムにしましたが、英語や絵文字、色名による入力などにも対応させれば、より柔軟なユーザビリティが得られるようになるかもしれません。