はじめに
本投稿はJava言語で学ぶデザインパターン入門のデザインパターンをまとめた記事です。今回はFactory Methodパターンになります。
まとめ一覧はこちら
Factory Methodパターン
Factory Methodとは
インスタンスの作り方をスーパークラスで定め、具体的な処理をサブクラスで行うパターンをFactory Methodパターンと呼びます。
オブジェクトの生成と具体的な処理を分離することで、より柔軟にオブジェクトを利用することができます。
Template Methodパターンをオブジェクト生成の場面に適応させたデザインパターンと言えますね。
簡単に言えば、オブジェクト生成を容易にするデザインパターンです。
FactoryパターンとFactory Methodパターン
FactoryパターンとFactory Methodパターンでは以下の違いがあります
- Factoryパターン
- Factoryクラスがオブジェクトの生成処理に加えて生成するオブジェクトの種類の変更をファクトリの処理の中で動的に行う
- Factory Methodパターン
- 生成するオブジェクトごとにファクトリを用意することで、オブジェクトの生成処理を柔軟に行える
- 役割はインスタンス生成の為のCreatorと実際のオブジェクトのProductのみ
- 抽象クラスと並行したConcreteCreatorとConcreteProductの関係性が成り立つ
- 抽象クラス
- Product :Productクラス
- Creator :Factoryクラス
- 具象クラス
- ConcreteCreator :AccountFactoryクラス
- ConcreteProduct :Accountクラス
- 抽象クラスの
createProduct
,registerProduct
の処理を実装 - 生成した
Product(Account)
はArrayList
で管理 -
Factory
のcreateProduct
宣言でサブクラスによるインスタンス生成が期待されるが、具体例以外では以下の方法も考えられる -
デフォルトの実装をする(サブクラスで実装されない時のため)
-
エラーの処理を書く(サブクラスで実装されなければ、エラーになる)
-
設計の意図を理解するためには、スーパークラスもサブクラス両方確認する必要がある
-
インスタンスの管理が必要になるため、
ConcreteCreator
でSingletonパターンが適応される場合が多い - 生成に関するパターン
- 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
Factoryパターンでは、生成するオブジェクトの種類の増加や生成処理手順が複雑化に伴って、ファクトリ内の処理が冗長で複雑化してしまいます。
FactoryMethodパターンでは、サブクラスで生成処理を行うため生成処理を簡潔にすることができますが、生成するオブジェクトの種類を変更する場合、ファクトリクラスを切り替える必要があります。
実際には、オブジェクトのによって生成処理・手順を分岐するFactoryパターンの使用場面の方が多いですね。Factoryパターンの具体例については後述
Factory Methodパターンのクラス図
具体例
今回は、指定した名前のアカウントを生成する簡単な例で紹介していきます。
Mainクラス
以下のように、create
に名前を渡してやるだけで名前に応じたインスタンスを生成してくれるFactoryパターン実装していきます
public static void main(String[] args) {
Factory factory = new AccountFactory();
Product account1 = factory.create("Ralph Johnson");
Product account2 = factory.create("Richard Helm");
Product account3 = factory.create("John Vlissides");
Product account4 = factory.create("Erich Gamma");
account1.use();
account2.use();
account3.use();
account4.use();
}
抽象クラス
Productクラス
use
(誰のアカウントを使っているか)メソッドを宣言
public abstract class Product {
public abstract void use();
}
Factoryクラス
インスタンス生成の為の抽象クラス
public abstract class Factory {
public final Product create(String owner) {
Product product = createProduct(owner);//※
registerProduct(product);
return product;
}
protected abstract Product createProduct(String owner);
protected abstract void registerProduct(Product product);
}
※インスタンス生成をメソッドに任せることで、クラス名を指定する必要がなくなる
インスタンス生成については後述
具象クラス
AccountFactoryクラス
public class AccountFactory extends Factory {
private List owners = new ArrayList();
protected Product createProduct(String owner) {
return new Account(owner);
}
protected void registerProduct(Product product) {
owners.add( ((Account)product).getOwner() );
}
public List getOwners() {
return owners;
}
}
Accountクラス
インスタンス作成時と、use
が呼び出された時にログを吐き出す
public class Account extends Product {
private String owner;
Account(String owner) {
System.out.println("Create account: " + owner);
this.owner = owner;
}
public void use() {
System.out.println("Use account: " + owner);
}
public String getOwner() {
return owner;
}
}
実行結果
Create account: Ralph Johnson
Create account: Richard Helm
Create account: John Vlissides
Create account: Erich Gamma
Use account: Ralph Johnson
Use account: Richard Helm
Use account: John Vlissides
Use account: Erich Gamma
名前に応じたインスタンスを生成できていることがわかりますね
追記
Factoryパターン
Factoryパターンによるオブジェクト生成の分岐の例を紹介したいと思います。
サルでもわかる 逆引きデザインパターンの例がわかりやすかったので、データファイルの読み込みの例を紹介したいと思います。
まず、データファイルの読み込みの処理で言うと、CSV形式であればCSVDataReader
オブジェクトを、XML形式であればXMLDataReader
などと、使用者側からすればオブジェクトによって条件分岐したくないですよね。
AbstractDataReader reader = null;
if (CSV == formatId) {
reader = new CSVDataReaderd();
} elseif (XML == formatId) {
reader = new XMLDataReaderd();
}
data = reader.read();
使用者はオブジェクトの使用のみに専念したいですね。
Factoryパターンを適用すると以下のようになります。
class Factory {
AbstractDataReader create(int formatId) {
AbstractDataReader reader = null;
if (CSV == formatId) {
reader = new CSVDataReaderd();
} elseif (XML == formatId) {
reader = new XMLDataReaderd();
}
return reader;
}
}
Factoryクラスで生成処理を分離するだけで、以下のようになります。
AbstractDataReader reader = null;
Factory factory = new Factory;
reader = factory(formatId);
data = reader.read();
使用者からしてみれば、オブジェクトの分岐や生成手順を気にする必要がなく、処理がかなり簡潔になりましたね。
新しいオブジェクトが増えた場合は、生成処理や条件式をFactory
クラスに追加するだけでいいんですね。
重要な点は、使用する側の処理を変更する必要がないところです。
サンプルコードについて
以下のレポジトリにソースコードをアップしてあります。
shoheiyokoyama/design-pattern_java