デザインパターン_クリエイティブパターン-《ファクトリーパターン》
ダークホースプログラマによるJavaデザインパターンの詳細解説、23のJavaデザインパターン(図表+フレームワークソースコード解析+実戦)をノートとしてまとめています。
概要
要件: コーヒー ショップの注文システムを設計します。
- コーヒー クラス (Coffee) を設計し、その 2 つのサブクラス (American Coffee [AmericanCoffee] と Latte Coffee [LatteCoffee]) を定義します。
- 次に、コーヒーを注文する機能を持つコーヒー ショップ クラス (CoffeeStore) を設計します。
特定のクラスは次のように設計されています。
Javaではすべてがオブジェクトであり、これらのオブジェクトを作成する必要があります。オブジェクトを作成するときに直接オブジェクトを新規作成すると、オブジェクトとの結合が厳しくなります。オブジェクトを置き換える場合は、新しいオブジェクトのすべての場所が必要になります。オブジェクトを変更する必要がありますが、これは明らかにソフトウェア設計のオープンクローズ原則に違反します。ファクトリを使用してオブジェクトを生成する場合は、ファクトリを処理してオブジェクトから完全に切り離すことしかできませんが、オブジェクトを置き換えたい場合は、ファクトリ内のオブジェクトを直接置き換えることができ、オブジェクトから切り離すという目的を達成できます。つまり、ファクトリー パターンの最大の利点は、切り離しであるということです。
このチュートリアルでは、3 つのファクトリーの使用が紹介されます。
- シンプルなファクトリーパターン(GOFに属さないクラシックなデザインパターン23個)
- ファクトリメソッドパターン
- 抽象的な工場パターン
シンプルなファクトリーパターン
Simple Factory はデザイン パターンではなく、プログラミングの習慣に似ています。
構造
単純なファクトリーには次の役割が含まれています。
- 製品の概要: 製品の仕様を定義し、製品の主な特徴と機能を説明します。
- 具体的な製品: 抽象的な製品のサブクラスを実装または継承します。
- コンクリートファクトリー: 製品を作成するメソッドを提供し、呼び出し元はこのメソッドを通じて製品を取得します。
達成
ここで、単純なファクトリを使用して上記のケースを改善します。クラス図は次のようになります。
-
ファクトリクラスのコードは次のとおりです。
/** * 简单咖啡工厂类,用来生产咖啡 */ public class SimpleCoffeeFactory { public Coffee createCoffee(String type) { Coffee coffee = null; if ("americano".equals(type)) { coffee = new AmericanoCoffee(); } else if ("latte".equals(type)) { coffee = new LatteCoffee(); } return coffee; } }
-
コーヒーショップのカテゴリー:
public class CoffeeStore { public Coffee orderCoffee(String type) { // 创建简单工厂 SimpleCoffeeFactory factory = new SimpleCoffeeFactory(); // 调用生产咖啡的方法 Coffee coffee = factory.createCoffee(type); // 加配料 coffee.addMilk(); coffee.addsugar(); return coffee; } }
ファクトリはオブジェクト作成の詳細を処理します。SimpleCoffeeFactory が存在すると、CoffeeStore クラスの orderCoffee() がこのオブジェクトのクライアントになります。後で Coffee オブジェクトが必要になった場合は、ファクトリから直接取得できます。これにより、Coffee実装クラスとの結合が解除されると同時に、CoffeeStoreオブジェクトとSimpleCoffeeFactoryファクトリオブジェクトの結合、ファクトリオブジェクトと商品オブジェクトの結合が新たに生成される。
後で新しい種類のコーヒーを追加する場合は、必ず SimpleCoffeeFactory のコードを変更する必要がありますが、これはオープンとクローズの原則に違反します。Meituan Waimai の作成など、ファクトリ クラスのクライアントが多数存在する可能性があります。この方法では、ファクトリ クラスのコードのみを変更する必要があり、他の変更操作は省略されます。
長所と短所
アドバンテージ
- オブジェクトの作成プロセスはカプセル化されており、オブジェクトはパラメータを通じて直接取得できます。将来的に顧客のコードを変更しないように、オブジェクトの作成をビジネス ロジック層から分離します。新しい製品を実装する場合は、元のコードを変更せずにファクトリ クラスを直接変更できるため、顧客がコードを変更する可能性が低くなります。コードを修正して拡張するのが簡単になります。
欠点がある
- 新しい製品を追加する場合も、ファクトリ クラスのコードを変更する必要がありますが、これは「オープンとクローズの原則」に違反します。
拡大
静的ファクトリー
- 開発においては、ファクトリ クラスでオブジェクトを作成する機能を静的と定義する人もいますが、これは静的ファクトリ パターンであり、23 のデザイン パターンの 1 つではありません。コードは以下のように表示されます:
public class SimpleCoffeeFactory {
public static Coffee createCoffee(String type) {
Coffee coffee = null;
if ("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffe;
}
}
ファクトリメソッドパターン
上記の例の欠点については、ファクトリ メソッド パターンを使用することで完全に解決でき、開閉の原則に完全に従うことができます。
コンセプト
オブジェクトを作成するためのインターフェイスを定義し、どの製品クラス オブジェクトをインスタンス化するかをサブクラスに決定させます。ファクトリ メソッドは、製品クラスのインスタンス化をそのファクトリ サブクラスに延期します。
構造
ファクトリ メソッド パターンの主な役割:
- Abstract Factory (Abstract Factory): 製品を作成するためのインターフェイスを提供し、呼び出し元はそれを使用して特定のファクトリのファクトリ メソッドにアクセスし、製品を作成します。
- コンクリート ファクトリ (ConcreteFactory): 主に、抽象ファクトリに抽象メソッドを実装して、特定の製品の作成を完了します。
- 抽象製品 (Product): 製品の仕様を定義し、製品の主な特徴と機能を説明します。
- 具体的なプロダクト(ConcreteProduct):抽象プロダクトロールで定義されたインターフェースを実装し、特定のファクトリによって作成され、特定のファクトリと1対1に対応します。
達成
ファクトリ メソッド パターンを使用して上記の例を改善します。クラス図は次のようになります。
コードは以下のように表示されます
-
抽象ファクトリー:
public interface CoffeeFactory { // 创建咖啡对象的方法 由具体的子类实现 Coffee createCoffee(); }
-
特定の工場:
// 拿铁咖啡工厂,专门用来生产拿铁咖啡 public class LatteCoffeeFactory implements CoffeeFactory { public Coffee createCoffee() { return new LatteCoffee(); } } // 美式咖啡工厂,专门用来生产拿式咖啡 public class AmericanCoffeeFactory implements CoffeeFactory { public Coffee createCoffee() { return new AmericanCoffee(); } }
-
コーヒーショップのカテゴリー:
public class CoffeeStore { private CoffeeFactory factory; public CoffeeStore(CoffeeFactory factory) { this.factory = factory; } public Coffee orderCoffee() { Coffee coffee = factory.createCoffee(); coffee.addMilk(); coffee.addsugar(); return coffee; } }
上記のコードから、製品カテゴリを追加するときは、それに応じて工場カテゴリも追加する必要があり、工場カテゴリのコードを変更する必要がないことがわかります。これにより、単純な工場モデルの欠点が解決されます。
ファクトリ メソッド パターンは、単純なファクトリ パターンをさらに抽象化したものです。ポリモーフィズムを使用することにより、ファクトリ メソッド パターンは単純なファクトリ パターンの利点を維持し、その欠点を克服します。
長所と短所
アドバンテージ
- ユーザーは、製品の具体的な製造プロセスを知らなくても、特定の工場の名前を知るだけで、必要な製品を入手できます。
- 新しい製品をシステムに追加するときは、元の工場に変更を加えることなく、特定の製品カテゴリと対応する特定の工場カテゴリを追加するだけでよく、開閉の原則を満たす必要があります。
欠点がある
- 製品が追加されるたびに、特定の製品カテゴリとそれに対応する特定の工場カテゴリを追加する必要があり、システムが複雑になります。
抽象的な工場パターン
上で紹介した工場方式モデルでは、動物を飼育するだけの畜産場、テレビのみを生産するテレビ工場、コンピューター ソフトウェアを専攻する学生のみを訓練する伝志ポッドキャストなど、1 種類の製品の生産を考慮しています。
これらの工場は同じ種類の製品のみを生産し、同じ種類の製品は同じレベルの製品と呼ばれます。つまり、ファクトリーメソッドモデルは同じレベルの製品の生産のみを考慮していますが、現実には、多くの製品が同じレベルの製品の生産を考慮しています。工場とは、複数のレベル (タイプ) を生産できる総合的な工場です。たとえば、電化製品工場ではテレビと洗濯機またはエアコンの両方が生産され、大学ではソフトウェア専攻と生物学専攻の両方が設置されます。
このセクションで紹介する抽象工場モデルは、多段階の製品の生産を考慮します。同じ特定の工場で生産される、異なるレベルの製品のグループを製品ファミリーと呼びます。下図の横軸は製品です。レベル、つまり同じカテゴリの製品、縦軸は製品ファミリー、つまり同じブランドの製品であり、同じブランドの製品は同じ工場で生産されます。
コンセプト
これは、関連オブジェクトまたは相互依存オブジェクトのグループを作成するためのインターフェイスをアクセス クラスに提供するパターン構造であり、アクセス クラスは、目的の製品の特定のクラスを指定せずに、同じファミリーのさまざまなレベルの製品を取得できます。
抽象ファクトリ パターンはファクトリ メソッド パターンのアップグレード バージョンで、ファクトリ メソッド パターンは 1 レベルの製品のみを生成しますが、抽象ファクトリ パターンは複数レベルの製品を生成できます。
構造
抽象ファクトリ パターンの主な役割は次のとおりです。
- Abstract Factory (Abstract Factory): プロダクトを作成するためのインターフェイスを提供します。これには、プロダクトを作成するための複数のメソッドが含まれており、異なるレベルの複数のプロダクトを作成できます。
- コンクリート ファクトリ: 主に、抽象ファクトリに複数の抽象メソッドを実装して、特定の製品の作成を完了します。
- 抽象製品 (Product): 製品の仕様を定義し、製品の主な特徴と機能を説明します。抽象ファクトリー パターンには複数の抽象製品があります。
- 具体的な製品: 抽象的な製品ロールによって定義されたインターフェイスを実装し、特定のファクトリによって作成され、特定のファクトリと多対 1 の関係を持ちます。
達成
現在のコーヒーショップのビジネスは、コーヒーの製造だけでなく、ティラミスや抹茶ムースなどのデザートの製造にも変化しています。ファクトリーメソッドモデルを使用する場合、ティラミス、抹茶ムース、ティラミスのファクトリーを定義する必要があり、抹茶ムース工場やデザート工場は爆発が起こりやすい。その中で、ラテ コーヒーとアメリカン コーヒーは 1 つの製品グレードであり、どちらもコーヒーです。ティラミスと抹茶ムースも 1 つの製品グレードです。ラテ コーヒーとティラミスは同じ製品ファミリー (つまり、両方ともイタリアン スタイルに属します)、アメリカーノと抹茶ムースは同じ製品ファミリーです (つまり、どちらもアメリカン フレーバーに属します)。したがって、このケースは抽象ファクトリ パターンを使用して実装できます。クラス図は次のとおりです。
コードは以下のように表示されます
-
抽象ファクトリー:
public interface DessertFactory { Coffee createCoffee(); Dessert createDessert(); }
-
特定の工場:
// 美式甜点工厂 public class AmericanDessertFactory implements DessertFactory { public Coffee createCoffee() { return new AmericanCoffee(); } public Dessert createDessert() { return new MatchaMousse(); } } // 意大利风味甜点工厂 public class ItalyDessertFactory implements DessertFactory { public Coffee createCoffee() { return new LatteCoffee(); } public Dessert createDessert() { return new Tiramisu(); } }
同じ製品ファミリを追加する場合は、対応するファクトリ クラスを追加するだけでよく、他のクラスを変更する必要はありません。
長所と短所
アドバンテージ
- 製品ファミリー内の複数のオブジェクトが連携して動作するように設計されている場合、クライアントは常に同じ製品ファミリーのオブジェクトのみを使用することが保証されます。
欠点がある
- 新しい製品を製品ファミリーに追加する必要がある場合、すべてのファクトリ クラスを変更する必要があります。これは、変更にはクローズされ、拡張にはオープンであるという設計原則に少し違反しています。
使用するシーン
- 作成するオブジェクトが、家電工場のテレビ、洗濯機、エアコンなど、相互に関連または依存する一連の製品ファミリーの場合。
- システムには複数の製品ファミリーがありますが、一度に使用されるのはそのうちの 1 つだけです。たとえば、特定のブランドの服や靴だけを着るのが好きな人がいます。
- 製品クラスライブラリはシステム内で提供され、すべての製品が同じインターフェースを持ち、クライアントは製品インスタンスの作成内容や内部構造に依存しません。
好き:
- インプットメソッドのスキンを変更し、セット全体をまとめて変更します。
- さまざまなオペレーティング システム用のプログラムを生成します。
スキーマ拡張
シンプルなファクトリー + 構成ファイルの分離
ファクトリ オブジェクトと製品オブジェクトの結合は、ファクトリ パターン + 構成ファイルを通じて分離できます。ファクトリクラスの設定ファイルにある完全なクラス名を読み込み、保存用のオブジェクトを作成し、クライアントが必要な場合は直接取得することができます。
-
ステップ 1: 構成ファイルを定義する
デモンストレーションの便宜上、プロパティ ファイルを構成ファイルとして使用します。
bean.properties
american=com.itheima.pattern.factory.config_factory.AmericanCoffee latte=com.itheima.pattern.factory.config_factory.LatteCoffee
-
ステップ 2: ファクトリ クラスを改善する
public class CoffeeFactory { /* * 加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储 */ // 1.定义容器对象存储咖啡对象 private static Map<String, Coffee> map = new HashMap(); // 2.加载配置文件,只需要加载一次 static { // 2.1 创建Properties对象 Properties p = new Properties(); // 2.2 调用p对象中的load方法进行配置文件的加载 InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties"); try { p.load(is); // 遍历Properties集合对象 Set<Object> keys = p.keySet(); for (Object key : keys) { // 根据键获取值(全类名) String className = p.getProperty((String) key); // 获取字节码对象 Class clazz = Class.forName(className); // 通过反射技术创建对象 Coffee obj = (Coffee) clazz.newInstance(); // 将名称和对象存储到容器中 map.put((String) key, obj); } } catch (Exception e) { e.printStackTrace(); } } // 根据名称获取对象 public static Coffee createCoffee(String name) { return map.get(name); } }
静的メンバー変数は、作成されたオブジェクトを格納するために使用されます (キーには名前が格納され、値には対応するオブジェクトが格納されます)。一方、構成ファイルの読み取りとオブジェクトの作成は静的コード ブロックで記述されるため、実行する必要があるのは 1 回だけです。
JDKソースコード解析 - Collection.iteratorメソッド
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("令狐冲");
list.add("风清扬");
list.add("任我行");
// 获取迭代器对象
Iterator<String> it = list.iterator();
// 使用迭代器遍历
while(it.hasNext()) {
String ele = it.next();
System.out.println(ele);
}
}
}
イテレータを使用してコレクションを走査し、コレクション内の要素を取得する上記のコードに精通している必要があります。単一列コレクションの反復子を取得する方法では、ファクトリ メソッドのパターンを使用します。クラス図を通して構造を見てみましょう。
Collection インターフェイスは抽象ファクトリ クラス、ArrayList は具象ファクトリ クラス、Iterator インターフェイスは抽象製品クラス、ArrayList クラスの Itr 内部クラスは具象製品クラスです。特定のファクトリ クラスでは、 iterator() メソッドによって特定の製品クラスのオブジェクトが作成されます。
他の:
- DateFormt クラスの getInstance() メソッドはファクトリ パターンを使用します。
- Calendar クラスの getInstance() メソッドはファクトリ パターンを使用します。