563
489

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

デザインパターン「Factory Method」

Last updated at Posted at 2016-02-20

はじめに

本投稿はJava言語で学ぶデザインパターン入門のデザインパターンをまとめた記事です。今回はFactory Methodパターンになります。
まとめ一覧はこちら

Factory Methodパターン

スクリーンショット 2016-02-14 15.31.29.png

Factory Methodとは

インスタンスの作り方をスーパークラスで定め、具体的な処理をサブクラスで行うパターンをFactory Methodパターンと呼びます。
オブジェクトの生成と具体的な処理を分離することで、より柔軟にオブジェクトを利用することができます。
Template Methodパターンをオブジェクト生成の場面に適応させたデザインパターンと言えますね。
簡単に言えば、オブジェクト生成を容易にするデザインパターンです。

FactoryパターンとFactory Methodパターン

FactoryパターンとFactory Methodパターンでは以下の違いがあります

Factoryパターン
Factoryクラスがオブジェクトの生成処理に加えて生成するオブジェクトの種類の変更をファクトリの処理の中で動的に行う

Factory Methodパターン
生成するオブジェクトごとにファクトリを用意することで、オブジェクトの生成処理を柔軟に行える

Factoryパターンでは、生成するオブジェクトの種類の増加や生成処理手順が複雑化に伴って、ファクトリ内の処理が冗長で複雑化してしまいます。
FactoryMethodパターンでは、サブクラスで生成処理を行うため生成処理を簡潔にすることができますが、生成するオブジェクトの種類を変更する場合、ファクトリクラスを切り替える必要があります。

実際には、オブジェクトのによって生成処理・手順を分岐するFactoryパターンの使用場面の方が多いですね。Factoryパターンの具体例については後述

Factory Methodパターンのクラス図

スクリーンショット 2016-02-14 15.53.14.png

  • 役割はインスタンス生成の為のCreatorと実際のオブジェクトのProductのみ
  • 抽象クラスと並行したConcreteCreatorとConcreteProductの関係性が成り立つ

具体例

今回は、指定した名前のアカウントを生成する簡単な例で紹介していきます。

Mainクラス

以下のように、createに名前を渡してやるだけで名前に応じたインスタンスを生成してくれるFactoryパターン実装していきます

Main.java
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(誰のアカウントを使っているか)メソッドを宣言

Product.java
public abstract class Product {

	public abstract void use();
}

Factoryクラス

インスタンス生成の為の抽象クラス

Factory.java
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クラス

  • 抽象クラスのcreateProduct, registerProductの処理を実装
  • 生成したProduct(Account)ArrayListで管理
AccountFactory.java
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が呼び出された時にログを吐き出す

Account.java
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

名前に応じたインスタンスを生成できていることがわかりますね

追記

  • FactorycreateProduct宣言でサブクラスによるインスタンス生成が期待されるが、具体例以外では以下の方法も考えられる

  • デフォルトの実装をする(サブクラスで実装されない時のため)

  • エラーの処理を書く(サブクラスで実装されなければ、エラーになる)

  • 設計の意図を理解するためには、スーパークラスもサブクラス両方確認する必要がある

  • インスタンスの管理が必要になるため、ConcreteCreatorでSingletonパターンが適応される場合が多い

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パターンを適用すると以下のようになります。

Factory.java
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

デザインパターン

参考文献

563
489
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
563
489

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?