Factoryパターンとは
どのオブジェクトを生成するかといった権限をサブクラスに委譲することにより、オブジェクト作成という変更が生じる部分を切り離し、オブジェクト間の関係性を疎結合にすることにより、保守性を高めることができる。
Factoryパターンの使い方
FactoryPatternを使う時と使わない時とでコードがどの程度変わるのか、比較しながら特長を確認していきます。
Factoryパターンを使わないプログラム場合
まずはFacrtoryパターンを使わないコードのサンプルを見てみましょう。
ここでは牛丼屋さんで牛丼を購入するプログラムが記述されています。
GyudonStoreクラスは牛丼を販売するクラスです。このコードで注目すべきポイントは、order()メソッドの中頃にあるif-else文です。ここでは引数の値によって、作成するオブジェクトを変えています。このコードの問題点は、条件式が変わる度にorder()メソッドを修正し、テストをしなければならないことです。条件式が変わる頻度が少ないと分かっているのであればこれでも構いませんが、そうでない場合は、このコードでは保守にかかるコストが高くなってしまいます。
public class GyudonStore {
public Gyudon order(GyudonTypeEnum type) {
Gyudon gyudon = new Gyudon();
if (type.equals(GyudonTypeEnum.NORMAL)) {
gyudon = new Gyudon();
} else if (type.equals(GyudonTypeEnum.CHEESE)) {
gyudon = new CheeseGyudon();
} else if (type.equals(GyudonTypeEnum.KIMCHI)) {
gyudon = new KimchiGyudon();
}
gyudon.prepare();
gyudon.serve();
return gyudon;
}
}
SimpleFactoryを使ったプログラムの場合
SimpleFactoryはデザインパターンではありませんが、一般的によく使われているイディオムです。
上記のような問題点を解決する方法の一つとして、SimpleFactoryがあります。これは、変更する部分を別のクラス、あるいはメソッドとして切り離し、修正箇所を最小限にする方法です。
SimpleGyudonFactoryクラスは、オブジェクト作成箇所を分離させたクラスです。このクラスは、GyudonStoreクラスから受け取った引数によって、異なるオブジェクトを作成し、返却する役割があります。そして、GyudonStoreクラスは、受け取ったオブジェクトを使って牛丼を作成しています。この場合、条件式が変わってもSimpleGyudonFactoryクラスだけを修正すればよく、GyudonStoreのorder()メソッドは変更する必要がなくなります。
public class SimpleGyudonFactory {
public Gyudon createGyudon(GyudonTypeEnum type) {
Gyudon gyudon = null;
if (type.equals(GyudonTypeEnum.NORMAL)) {
gyudon = new Gyudon();
} else if (type.equals(GyudonTypeEnum.CHEESE)) {
gyudon = new CheeseGyudon();
} else if (type.equals(GyudonTypeEnum.KIMCHI)) {
gyudon = new KimchiGyudon();
}
return gyudon;
}
}
public class GyudonStore {
SimpleGyudonFactory simpleFactory;
public GyudonStore(SimpleGyudonFactory factory) {
this.simpleFactory = factory;
}
public Gyudon order(GyudonTypeEnum type) {
// SimpleGyudonFactoryに注文の種類を渡し、作成の準備をする
Gyudon gyudon = simpleFactory.createGyudon(type);
gyudon.prepare();
gyudon.serve();
return gyudon;
}
}
実行結果は以下の通りです。
public class GyudonBasicTest {
public static void main(String[] args) {
// create a Gyudon shop
GyudonStore store = new GyudonStore(new SimpleGyudonFactory());
Gyudon normal = new Gyudon();
normal = store.order(GyudonTypeEnum.NORMAL);
Gyudon cheese = new Gyudon();
cheese = store.order(GyudonTypeEnum.CHEESE);
Gyudon kimchi = new Gyudon();
kimchi = store.order(GyudonTypeEnum.KIMCHI);
}
}
作業①:トッピングを選びます
トッピング: なし
作業②:盛り付けをします
牛丼が完成しました
++++++++++++++++++++
作業①:トッピングを選びます
トッピング: チーズ
作業②:盛り付けをします
チーズ牛丼が完成しました
++++++++++++++++++++
作業①:トッピングを選びます
トッピング: キムチ
作業②:盛り付けをします
キムチ牛丼が完成しました
++++++++++++++++++++
UML
Factoryパターンを使ったプログラムの場合
SimpleFactoryで使用した牛丼販売店をフランチャイズ化することを考えてみましょう。日本と韓国でそれぞれ牛丼を販売することとします。メニューは普通の牛丼、チーズ牛丼、キムチ牛丼の三つですが、両国では食材が異なるため、別々のクラスを作る必要があります。
GyudonStoreクラスは、オブジェクト生成箇所をcreateGyudon()メソッド内に記述し、抽象化します。SimpleFactoryのように全く別のクラスとして外出ししてしまうと、管理するのにコストがかかってしまいますし、createGyudon()メソッドの内容は各Factoryにおいてもほぼ同じ処理であるため、ここでは抽象化が適していると考えます。
// abstract class to sell Gyudon(beef on the rice)
public abstract class GyudonStore {
public Gyudon order(GyudonTypeEnum type) {
Gyudon gyudon = createGyudon(type);
gyudon.prepare();
gyudon.serve();
return gyudon;
}
abstract Gyudon createGyudon(GyudonTypeEnum type);
}
JapaneseGyudonStoreクラスとKoreanGyudonStoreクラスはGyudonStoreクラスを継承しており、それぞれのオブジェクトを返却しています。
仮に店舗が増えたとしても、その店舗のクラスを作るだけであり、既存のクラスを修正する必要がないため、保守性を保つことができます。
public class JapaneseGyudonStore extends GyudonStore {
public Gyudon createGyudon(GyudonTypeEnum type) {
if (type.equals(GyudonTypeEnum.NORMAL)) {
return new JapaneseGyudon();
} else if (type.equals(GyudonTypeEnum.CHEESE)) {
return new JapaneseCheeseGyudon();
} else if (type.equals(GyudonTypeEnum.KIMCHI)) {
return new JapaneseKimchiGyudon();
} else {
return null;
}
}
}
public class KoreanGyudonStore extends GyudonStore {
public Gyudon createGyudon(GyudonTypeEnum type) {
if (type.equals(GyudonTypeEnum.NORMAL)) {
return new KoreanGyudon();
} else if (type.equals(GyudonTypeEnum.CHEESE)) {
return new KoreanCheeseGyudon();
} else if (type.equals(GyudonTypeEnum.KIMCHI)) {
return new KoreanKimchiGyudon();
} else {
return null;
}
}
}
牛丼を作るのに必要な各クラスは下記の通りです。基本的にはGyudonクラスを継承して使用しています。
ublic class Gyudon {
String name;
String beef;
String rice;
List<String> topping = new ArrayList<String>();
public String getName() {
return name;
}
public void prepare() {
System.out.println("orderd " + this.getName());
System.out.println("work①:choose topping");
System.out.print("chosen topping is: ");
this.chooseTopping().stream().forEach(str -> System.out.print(str + " "));
System.out.println();
System.out.println("work②: serve");
}
public List<String> chooseTopping() {
return topping;
}
public void serve() {
System.out.println("cooked Gyudon");
System.out.println("++++++++++++++++++++");
}
}
public class JapaneseGyudon extends Gyudon {
public JapaneseGyudon() {
this.name = "Japanese Gyudon";
this.beef = "Japanese beef";
this.rice = "Japanese rice";
}
public void serve() {
System.out.println("cooked Japanese Gyudon");
System.out.println("++++++++++++++++++++");
}
}
実行結果は以下の通りです。
public class GyudonFactoryTest {
public static void main(String[] args) {
Gyudon gyudon = new Gyudon();
// buy at Japanese shop
GyudonStore japan = new JapaneseGyudonStore();
gyudon = japan.order(GyudonTypeEnum.NORMAL);
gyudon = japan.order(GyudonTypeEnum.CHEESE);
gyudon = japan.order(GyudonTypeEnum.KIMCHI);
// buy at Korean shop
GyudonStore korea = new KoreanGyudonStore();
gyudon = korea.order(GyudonTypeEnum.NORMAL);
gyudon = korea.order(GyudonTypeEnum.CHEESE);
gyudon = korea.order(GyudonTypeEnum.KIMCHI);
}
}
orderd Japanese Gyudon
work①:choose topping
chosen topping is:
work②: serve
cooked Japanese Gyudon
++++++++++++++++++++
orderd Japanese Cheese Gyudon
work①:choose topping
chosen topping is: Japanese cheese
work②: serve
cooked Japanese Cheese Gyudon
++++++++++++++++++++
orderd Japanese Kimchi Gyudon
work①:choose topping
chosen topping is: Japanese Kimchi
work②: serve
cooked Japanese Kimchi Gyudon
++++++++++++++++++++
orderd Korean Gyudon
work①:choose topping
chosen topping is: Korean spice
work②: serve
cooked Korean Gyudon
++++++++++++++++++++
orderd Korean Cheese Gyudon
work①:choose topping
chosen topping is: Korean cheese Korean spice
work②: serve
cooked Korean Cheese Gyudon
++++++++++++++++++++
orderd Korean Kimchi Gyudon
work①:choose topping
chosen topping is: Korean Kimchi Korean Spice
work②: serve
cooked Korean Kimchi Gyudon
++++++++++++++++++++
UML
Factoryパターンのメリット・デメリット
メリット
- 作成するオブジェクトの決定をサブクラスに行わせることで、オブジェクトの作成をカプセル化できる。オブジェクトの作成は頻繁に変更される箇所であり、その部分をカプセル化することにより、変更の少ない設計となる
- オブジェクト作成クラスを集中させることで、コードの重複を減らし、保守を一箇所に集中させることができる。その結果、保守しやすい設計となる
- クライアントがインタフェースに依存することができる。つまり、柔軟性の高い設計が可能となり、将来の拡張が可能になる
デメリット
- オブジェクト作成に関して変更がある度に、オブジェクト作成クラスを変更しなければならない。特に変更の頻度が高くなれば、問題となりうる
- 抽象に依存しているため、コードが読みにくくなることがある
重要な設計原則
- 変更する部分をカプセル化する
- 実装ではなく、インタフェースに対してプログラミングする
- 抽象に依存する。具象に依存してはならない
- 相互にやり取りするオブジェクト間には、疎結合設計にする
- クラスは拡張に対しては開かれた状態であるべきだが、変更に対しては閉じた状態であるべきである
JDKにおける実装例
- java.util.Calendar
- java.text.NumberFormatのgetInstance()メソッド
Reference
- Head First デザインパターン〜頭とからだで覚えるデザインパターンの基本
- techscore