一、概述
工廠是一種創建型的設計模式,常用於封裝變化,一般遵循那里有變化就封裝那里的原則。這里我們以一個快餐店為示例講解,FastFood表示快餐,KFC表示肯德基,Mac表示麥當勞。
FastFood.java
package DP02.demo21; /**快餐*/ public abstract class FastFood { /**品牌*/ public String brand; /**展示*/ public abstract void show(); }
KFC.java
package DP02.demo21; public class KFC extends FastFood { @Override public void show() { this.brand="肯德基"; System.out.println("歡迎來到"+this.brand); } }
Mac.java
package DP02.demo21; public class Mac extends FastFood { @Override public void show() { this.brand="麥當勞"; System.out.println("歡迎來到"+this.brand); } }
Student.java
package DP02.demo21; /** * 學生 */ public class Student { public static void main(String[] args) { KFC kfc = new KFC(); kfc.show(); } }
運行結果:
從Student類中可以看到學生只允許吃KFC,如果他想吃別的東西就不允許了,違背DIP(依賴倒轉原則),解決:
package DP02.demo21; /** * 學生 */ public class Student { public static void main(String[] args) { //這里可以指定任意FastFood子類,LSP //如果這里是穩定的,不會經常變化,則代碼是沒有問題的 //如果這里要不斷的變化,new KFC(),new Mac,new Kongfu()... FastFood kfc = new KFC(); kfc.show(); } }
new KFC()就是一個變化點,封裝。
二、簡單工廠(Simple Factory)
簡單工廠並不是GOF所著的書中提出的一種設計模式,但這種模式是學習設計模式一個很好的入口點,讓我們能更容易理解設計模式是如何達到設計原則的要求的。
簡單工廠設計模式是一種創建型的模式,主要是用來解決對象的創建問題。
上面的類圖反映的就是將一組相似對象(繼承自同一個類)的實例的創建放到另外一個對象中完成,即通過 ProductFactory 這個類來按需創建對象
在第一節中我們提出了“封裝變化”的概念,這里使用簡單工廠解決問題,代碼如下:
FastFoodFactory.java
package DP02.demo21; /** * 快餐工廠 */ public class FastFoodFactory { /*** * 用於實現不同類型快餐品牌的創建 * @param brand 類型 * @return 快餐品牌 */ public static FastFood GetFastFood(String brand) { switch (brand) { case "KFC": return new KFC(); case "Mac": return new Mac(); default: throw new IllegalArgumentException("沒有該品牌"); } } }
Student.java
package DP02.demo21; import java.util.Scanner; /** * 學生 */ public class Student { public static void main(String[] args) { //這里可以指定任意FastFood子類,LSP //如果這里是穩定的,不會經常變化,則代碼是沒有問題的 //如果這里要不斷的變化,new KFC(),new Mac,new Kongfu()... Scanner input=new Scanner(System.in); System.out.print("您想吃什么:"); String brand=input.next(); FastFood kfc = FastFoodFactory.GetFastFood(brand); kfc.show(); } }
運行結果:
簡單工廠的優點:
簡單工廠的工廠類中包含了必要的邏輯判斷,這樣就可以根據客戶端的選擇條件來動態的實例化相關的類,對於客戶端來說,其去除了與具體產品之間的依賴
簡單工廠的缺點:
違背了開-閉原則
三、工廠方法(Factory Method)
工廠方法模式,定義了一個用於創建對象的接口,讓子類來決定要實例化哪一個類,工廠方法讓類把實例化延遲到其子類。
在工廠方法模式中主要有以下幾個組成部分:
抽象工廠:是工廠方法模式的核心,任何在模式中創建的具體工廠必須實現這個接口。
具體工廠:這是實現抽象工廠接口的具體工廠類,包含與應用程序密切相關的邏輯,並且受到應用程序調用以創建產品對象。
抽象產品:方法方法模式所要創建的對象的父類型,也就是產品對象的共同父類或共同擁有的接口。
具體產品:這是實現了抽象產品所定義的接口的具體產品類的實例。這是客戶端最終需要的東西。
示例:
KFC肯德基,Mac麥當勞,Chips薯條,Ham漢堡
Chips.java
package DP02.demo22; /**抽象產品,薯條*/ public abstract class Chips { /**展示薯條信息*/ public abstract void info(); }
KFCChips.java
package DP02.demo22; /**實體產品,肯德基薯條*/ public class KFCChips extends Chips { @Override public void info() { System.out.println("肯德基薯條"); } }
MacChips.java
package DP02.demo22; /**實體產品,麥當勞薯條*/ public class MacChips extends Chips { @Override public void info() { System.out.println("麥當勞薯條"); } }
Ham.java
package DP02.demo22; /**抽象產品,漢堡*/ public abstract class Ham { /**顯示漢堡信息*/ public abstract void Show(); }
KFCHam.java
package DP02.demo22; /**實體產品 肯德基漢堡*/ public class KFCHam extends Ham { @Override public void Show() { System.out.println("肯德基漢堡"); } }
MacHam.java
package DP02.demo22; /** * 實體產品 麥當勞漢堡 */ public class MacHam extends Ham { @Override public void Show() { System.out.println("麥當勞漢堡"); } }
FastFoodFactory.java
package DP02.demo22; /** * 抽象工廠 快餐工廠 */ public abstract class FastFoodFactory { /** * 生產漢堡 */ public abstract Ham CreateHam(); /** * 生產薯條 */ public abstract Chips CreateChips(); }
KFCFactory.java
package DP02.demo22; /**實體工廠 肯德基*/ public class KFCFactory extends FastFoodFactory { @Override public Ham CreateHam() { return new KFCHam(); } @Override public Chips CreateChips() { return new KFCChips(); } }
MacFactory.java
package DP02.demo22; /** * 實體工廠 麥當勞 */ public class MacFactory extends FastFoodFactory { @Override public Ham CreateHam() { return new MacHam(); } @Override public Chips CreateChips() { return new MacChips(); } }
Client.java
package DP02.demo22; /**測試類*/ public class Client { public static void main(String[] args) { /**創建食品工廠*/ FastFoodFactory factory=new KFCFactory(); Chips chips=factory.CreateChips(); chips.info(); factory.CreateHam().Show(); factory=new MacFactory(); factory.CreateChips().info(); factory.CreateHam().Show(); } }
運行結果:
但是當產品種類非常多時,就會出現大量的與之對應的工廠類,所以我建議在這種情況下使用簡單工廠模式與工廠方法模式相結合的方式來減少工廠類:即對於產品樹上類似的種類(一般是樹的葉子中互為兄弟的)使用簡單工廠模式來實現。
當然特殊的情況,就要特殊對待了:對於系統中存在不同的產品樹,而且產品樹上存在產品族,那么這種情況下就可能可以使用抽象工廠模式了。
四、抽象工廠(Abstract Factory)
1、提供一系列相互依賴對象的創建工作
2、封裝對象常規的創建方法(new)
3、提供統一調用數據訪問方法的方式
4、避免調用數據訪問方法和具體對象創建工作的緊耦合
1、用抽象工廠生產抽象產品
2、用實體工廠生產實體產品
3、用抽象產品提供實體產品訪問接口
4、用實體產品實現自己的功能
在工廠方法的基礎上修改FastFoodFactory.java,代碼如下:
package DP02.demo23; /** * 抽象工廠 快餐工廠 */ public abstract class FastFoodFactory { /** * 生產漢堡 */ public abstract Ham CreateHam(); /** * 生產薯條 */ public abstract Chips CreateChips(); /*** *根據brand類型返回不同的工廠 * @param brand 工廠類型 品牌 * @return 實體工廠 */ public static FastFoodFactory GetFactory(String brand){ switch(brand){ case "KFC": return new KFCFactory(); case "Mac": return new MacFactory(); default: throw new IllegalArgumentException("不存在的工廠類型"); } } }
測試Client.java
package DP02.demo23; import java.util.Scanner; /**測試類*/ public class Client { public static void main(String[] args) { /**創建食品工廠*/ Scanner input=new Scanner(System.in); System.out.print("您想吃什么:"); String brand=input.next(); /**根據品牌生成實體工廠*/ FastFoodFactory factory=FastFoodFactory.GetFactory(brand); factory.CreateChips().info(); factory.CreateHam().Show(); } }
運行:
抽象工廠模式的用意為:給客戶端提供一個接口,可以創建多個產品族中的產品對象。而且使用抽象工廠模式還要滿足一下條件:
1.系統中有多個產品族,而系統一次只可能消費其中一族產品
2.同屬於同一個產品族的產品一起使用時。
五、萬能工廠
先來看簡單工廠中選擇不同類型的產品時的代碼:
package DP02.demo21; /** * 快餐工廠 */ public class FastFoodFactory { /*** * 用於實現不同類型快餐品牌的創建 * @param brand 類型 * @return 快餐品牌 */ public static FastFood GetFastFood(String brand) { switch (brand) { case "KFC": return new KFC(); case "Mac": return new Mac(); default: throw new IllegalArgumentException("沒有該品牌"); } } }
當添加新的品牌時switch必須要修改,否則不會被選擇。用反射可以削除switch,將字符串直接轉換成一個對象。
修改簡單工廠中的選擇品牌的代碼:
package DP02.demo21; /** * 快餐工廠 */ public class FastFoodFactory { /*** * 用於實現不同類型快餐品牌的創建 * @param brand 類型 * @return 快餐品牌 */ public static FastFood GetFastFood(String brand) { switch (brand) { case "KFC": return new KFC(); case "Mac": return new Mac(); default: throw new IllegalArgumentException("沒有該品牌"); } } /*** * 用於實現不同類型快餐品牌的創建 * @param brand 類型 * @return 快餐品牌 */ public static FastFood GetFastFoodPro(String brand) { try { return (FastFood) Class.forName("DP02.demo21." + brand).newInstance(); } catch (Exception e) { e.printStackTrace(); } return null; } }
增加一個新的品牌:
package DP02.demo21; public class HLS extends FastFood { @Override public void show() { System.out.println("歡迎光臨華菜士!"); } }
客戶端調用:
package DP02.demo21; import java.util.Scanner; /** * 學生 */ public class Student { public static void main(String[] args) { //這里可以指定任意FastFood子類,LSP //如果這里是穩定的,不會經常變化,則代碼是沒有問題的 //如果這里要不斷的變化,new KFC(),new Mac,new Kongfu()... Scanner input=new Scanner(System.in); System.out.print("您想吃什么:"); String brand=input.next(); //FastFood food1 = FastFoodFactory.GetFastFood(brand); //food1.show(); FastFood food2 = FastFoodFactory.GetFastFoodPro(brand); food2.show(); } }
運行結果:
新增了一HSL的產品品牌,但不需要修改任何代碼,符合OCP原則,程序正常運行。修改抽象工廠方法基本一樣。
反射機制指的是程序在運行時能夠動態獲取語言本身的信息,能夠使我們很方便的創建靈活的代碼,這些代碼可以再運行時裝配,無需在組件之間進行源代碼鏈接
1、反射提高了程序的靈活性和擴展性。
2、降低耦合性,提高自適應能力。
3、它允許程序創建和控制任何類的對象,無需提前硬編碼目標類。
六、示例
示例:https://coding.net/u/zhangguo5/p/DP01/git
七、視頻
視頻:https://www.bilibili.com/video/av15867320/
八、作業與資料
8.1、作業
a)、請使用簡單工廠完成一個水果工廠:
b)、請將簡單工廠修改成萬能工廠,添加新的水果類型時不需要修改switch
c)、請完成一個抽象工廠的示例,根據不同的國家產生不同的水果族,如:
中國 美國 德國
Apple Apple Apple
Orange Orange Orange
d)、請使用抽象工廠完成一個學員管理功能,實現展示學員與新增學員,要求在配置文件中動態切換數據庫,也就是說當我在db.properties中設置DataBase=MySQL時系統立即使用MySQL作為當前運行的數據庫,如果我設置為SQL Server則當前系統的數據庫為SQL Server;也可以根據自己的選擇使用任意數據庫。