2. ファクトリ「メソッド」パターン & 「抽象」ファクトリパターン
「ファクトリ」とは何か?
ファクトリとは、単純に自動販売機を考えると分かりやすいです。「飲み物」の自動販売機からは「飲み物」が出てきて、「お菓子」の自動販売機からは「お菓子」が出てきます。自動販売機(VendingMachine)と商品(Product)はどちらもインターフェースで定義されているため、「商品(Product)」を飲み物として実装すれば、「自動販売機(VendingMachine)」は飲み物自動販売機として実装すれば良いのです。
- インターフェース:「自動販売機」と「商品」
VendingMachine machine;
Product product;
- クラス:「飲み物自動販売機」と「飲み物」
VendingMachine machine = new BeverageVendingMachine();
Product product = machine.get();
ファクトリとは、上記のように状況に応じて実装することを指します。
「ファクトリメソッド」とは何か?
ファクトリメソッドは、文字通り状況に応じて実装するファクトリの役割を担うメソッドです。どのようなProductであるかに応じてProduct実装を返す関数で、上記の例ではmachine.get()に該当します。
ファクトリメソッドパターン
ファクトリメソッドパターンは、インターフェース内に定義されている「ファクトリメソッド」を、状況に応じた実装オブジェクトを返すように、状況に合わせて具体的な「実装ファクトリメソッド」を作成するものです。これを使用することで得られるメリットを見てみましょう。
- 非推奨:状態に応じた商品返却 - 状態の内部化
- 状態管理の保守性が低下します。
- 状態管理の重複が増加します。
- 本例では
get関数のみを使用していますが、関数が増えるにつれてif-else文は繰り返されるでしょう。
- 本例では
class VendingMachine {
public Product get(String type) {
if (type == "beverage") {
return new Beverage();
} else if (type == "snack") {
return new Snack();
}
}
}
VendingMachine machine = new VendingMachine();
Product product = machine.get('beverage');
- 推奨:状態に応じた商品返却 - 状態の外部化(依存性逆転)
- 状態管理の一元化(保守性、重複性の解決)
interface VendingMachine {
public Product get();
}
class BeverageVendingMachine implements VendingMachine {
public Product get() {
return new Beverage();
}
}
class SnackVendingMachine implements VendingMachine {
public Product get() {
return new Snack();
}
}
VendingMachine machine = new BeverageVendingMachine();
Product product = machine.get();
VendingMachineインターフェース内のファクトリメソッドを、状況に合わせてどのような実装を返すか実装すれば良いだけです。もう使用しない状況の実装クラスは単に削除すればよく、追加の状況に応じた処理が必要な場合はクラスを作成するだけで済みます。
抽象ファクトリパターン
ところで、飲み物の種類がますます多様になると仮定しましょう。ファクトリメソッドパターンを使用した場合、次のように毎回クラスが生成されますが、このままで本当に良いのでしょうか?どんなに多くの種類の飲み物が生まれても、結局「飲み物(Beverage)」という分類は変わりません。ただ中身が変わるだけです。
interface VendingMachine {
public Product get();
}
class FruitBeverageVendingMachine implements VendingMachine {
public Product get() {
return new FruitBeverage();
}
}
class SparklingBeverageVendingMachine implements VendingMachine {
public Product get() {
return new SparklingBeverage();
}
}
class TeaBeverageVendingMachine implements VendingMachine {
public Product get() {
return new TeaBeverage();
}
}
...
FruitBeverage、SparklingBeverage、TeaBeverageはすべて飲み物という分類に含まれ、実際に3つの内部クラスのロジックはわずかな違いしかなく、ほとんどが同じでしょう。Beverageという汎用的な飲み物クラスを作成し、その中に入れる材料だけを変えれば良いはずです。そうすることで、...Beverageクラスが量産されるという重複を以下のように解決します。
interface VendingMachine {
Maker maker;
public Product get() {
return new Beverage(maker);
}
}
class FruitBeverageVendingMachine implements VendingMachine {
Maker maker = new FruitBeverageMaker();
}
class SparklingBeverageVendingMachine implements VendingMachine {
Maker maker = new SparklingBeverageMaker();
}
class TeaBeverageVendingMachine implements VendingMachine {
Maker maker = new TeaBeverageMaker();
}
...
「ファクトリメソッドパターン」はファクトリ「メソッド」が__状況に応じた実装オブジェクト__を返すのに対し、「抽象ファクトリパターン」はメソッドはカテゴリオブジェクトを単に返すだけで、__状況に応じた詳細な実装__は「抽象」ファクトリインターフェースに委譲したものです。
- 「ファクトリメソッドパターン」は状況に応じて1、2、3の実装オブジェクトを返し、
- 「抽象ファクトリパターン」はAカテゴリのオブジェクトを返し、
- 状況に応じた実装を「抽象ファクトリインターフェース」に委譲したものです。
- メソッドは機械的にオブジェクトを返すだけで、何を返すかを知る必要がありません。
- ファクトリメソッドパターンにおける状況に応じた生成/返却の役割を、さらに依存性逆転によって担わせたと言えます。
- 状況に応じた実装を「抽象ファクトリインターフェース」に委譲したものです。
しかし、...Beverageクラスが量産されるのと...BeverageMakerクラスが量産されるのは、実質的に同じことではないでしょうか?はい、同じです。では、なぜわざわざ状況に応じた実装の責任を抽象ファクトリインターフェースに委譲したのでしょうか?
VendingMachineは製品を提供する役割だけを持ち、製品生産に関する責任はBeverageMakerが分担するためです。