Java設計模式之簡單工廠 工廠方法 抽象工廠深度總結
工廠模式介紹
工廠模式也是非常常見的設計模式之一,其屬於創建型模式。工廠模式分類:簡單工廠(Simple Factory)、工廠方法(Factory Method)、抽象工廠(Abstract Factory),嚴格來講,簡單工廠不屬於工廠設計模式。
好處
- 解耦:工廠模式主要是為瞭對象的創建和使用的分離
- 降低代碼重復:如果對象創建很復雜,則每次創建都需要重復很多代碼,通過工廠包裝起來可以減少重復代碼
- 方便維護:如果創建對象的邏輯有修改,則隻需要修改工廠內代碼,而不用修改每個創建對象的代碼。
常見的應用
- Spring中的BeanFactory.getBean(beanName)
- 日志當中的LoggerFactory.getLogger(name)
- Java加密時 KeyGenerator keygen=KeyGenerator.getInstance(“AES”);
- RabbitMQ的Java客戶端創建連接 :
//創建連接工廠 ConnectionFactory factory = new ConnectionFactory("192.168.74.4"); // 通過連接工廠獲取連接 Connection connection = factory.newConnection();
簡單工廠(Simple Factory)
簡單工廠並不在23種設計模式之中,屬於特殊的工廠模式,應用的相對來說少一點。客戶端實例化對象的時候不用通過new Audi()的方式,而是可以通過往統一工廠傳入相應的條件返回對應的實例對象(主要通過if-else或者switch-case進行判斷,違背瞭開閉原則),屏蔽瞭實例化對象的具體邏輯細節。
適用場景
- 需要創建的對象較少。
- 客戶端不關心對象的創建過程。
角色分配:
- 工廠(Factory)角色:簡單工廠模式的核心,它負責實現創建所有實例的內部邏輯。工廠類可以被外界直接調用,創建所需的產品對象。
- 抽象產品(Product)角色 :簡單工廠模式所創建的所有對象的父類,它負責描述所有實例所共有的公共接口。
- 具體產品(Concrete Product)角色:簡單工廠模式的創建目標,所有創建的對象都是充當這個角色的某個具體類的實例。
其UML類圖如下所示:
應用案例:
- Java加密時 KeyGenerator keygen=KeyGenerator.getInstance(“AES”);
- DateFormat類中的public final static DateFormat getDateInstance(int style)
優缺點:
- 優點:實現簡單,隱藏瞭創建對象的細節,創建對象的邏輯修改時,客戶端不用進行修改。
- 缺點:違背瞭開閉原則,每次新增刪除子類的時候都要修改工廠的邏輯,而且創建對象的邏輯都包含在工廠內,後面子類太多的話這塊代碼會非常多,難以維護。
簡單工廠實現:
我們創建一個Car接口,裡面包含一個getName方法用以返回具體的品牌名稱,接口有兩個實現類Audi和Bmw,分別返回瞭品牌名稱 “Audi” 和 “Bmw”。
package com.wkp.designpattern.factory; //汽車接口 public interface Car { //返回汽車的品牌 public String getName(); }
package com.wkp.designpattern.factory; public class Audi implements Car { public String getName() { return "Audi"; } }
package com.wkp.designpattern.factory; public class Bmw implements Car { public String getName() { return "Bmw"; } }
下面是簡單工廠的核心,用於創建對象
package com.wkp.designpattern.simple.factory; import com.wkp.designpattern.factory.Audi; import com.wkp.designpattern.factory.Bmw; import com.wkp.designpattern.factory.Car; public class SimpleFactory { public Car getCar(String name){ if("Audi".equals(name)){ return new Audi(); }else if("Bmw".equals(name)){ return new Bmw(); }else{ System.out.println("沒法生產這個車"); return null; } } }
測試代碼如下:
package com.wkp.designpattern.simple.factory; import com.wkp.designpattern.factory.Car; public class SimpleFactoryTest { public static void main(String[] args) { SimpleFactory factory = new SimpleFactory(); Car car1 = factory.getCar("Audi"); System.out.println(car1.getName()); Car car2 = factory.getCar("Bmw"); System.out.println(car2.getName()); } }
輸出結果如下:
Audi
Bmw
這裡每增加一個新的子類,getCar方法就要添加if判斷,刪除瞭子類這裡也要修改,顯然違反瞭開閉原則,而且創建對象的邏輯全部都在這個方法中,隨著創建對象的增加,這裡的邏輯會非常多。當然我們可以通過反射的方式對上面的工廠進行改進如下:
//利用反射改進的簡單工廠,添加的時候不用修改getCar方法 public class ReflectSimpleFactory { //參數className為完整類名 public Car getCar(String className){ Car obj=null; try { obj=(Car) Class.forName(className).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return obj; } }
public class ReflectSimpleFactoryTest { public static void main(String[] args) { ReflectSimpleFactory factory = new ReflectSimpleFactory(); Car car1 = factory.getCar("com.wkp.designpattern.factory.Audi"); System.out.println(car1.getName()); Car car2 = factory.getCar("com.wkp.designpattern.factory.Bmw"); System.out.println(car2.getName()); } }
輸出結果不變,也符合瞭開閉原則,但是要傳入完整類名也不方便,可以通過xml或者配置文件的方式進行改進。
工廠方法(Factory Method)
工廠方法的應用是最多的,工廠方法中不再提供統一的工廠創建對象,而是針對不同的對象提供不同的工廠。定義一個創建對象的接口,讓其子類自己決定實例化哪一個工廠類,創建過程延遲到子類進行。
適用場景
- 當一個類不知道它所必須創建的對象的類的時候:工廠方法模式中客戶端不需要知道對象的名稱,隻需要知道要創建的對象對應的工廠即可。
- 當一個類希望由它的子類來指定它所創建的對象的時候:工廠方法模式中定義瞭一個Factory接口,該接口包含一個創建對象的抽象方法,具體的創建對象的邏輯由其實現類完成。
- 當類將創建對象的職責委托給多個幫助子類中的某一個,並且你希望將哪一個幫助子類是代理者這一信息局部化的時候。
角色分配:
- 抽象工廠(Abstract Factory)角色:是工廠方法模式的核心,提供瞭創建對象的接口,任何在模式中創建的對象的工廠類必須實現這個接口。
- 具體工廠(Concrete Factory)角色:這是實現抽象工廠接口的具體工廠類,包含與應用程序密切相關的邏輯,並且受到應用程序調用以創建某一種產品對象。
- 抽象產品(AbstractProduct)角色 :工廠方法模式所創建的對象的超類型,也就是產品對象的共同父類或共同擁有的接口。
- 具體產品(Concrete Product)角色 :這個角色實現瞭抽象產品角色所定義的接口。某具體產品有專門的具體工廠創建,它們之間往往一一對應
其UML類圖如下所示:
應用案例:
- spring-data-redis中創建redis連接的地方,RedisConnectionFactory接口提供瞭創建連接的方法RedisConnection getConnection(),該工廠的兩個實現類JedisConnectionFactory ,LettuceConnectionFactory分別用於創建jedis和lettuce連接
優缺點:
- 優點:符合開閉原則,添加子類時隻需要添加對應的工廠類即可,而不用修改原有的工廠類,每個對象的創建邏輯都在對應的工廠類中,代碼邏輯清晰,易於維護。
- 缺點:每次增加一個產品時,都需要增加一個具體類和對象實現工廠,使得系統中類的個數成倍增加,在一定程度上增加瞭系統的復雜度,同時也增加瞭系統具體類的依賴。
工廠方法實現:
定義一個Factory接口,裡面提供瞭一個getCar()方法,具體的創建邏輯由其實現類去完成。
public interface Factory { public Car getCar(); }
下面是Factory接口的具體實現類,用於創建對應的對象
public class AudiFactory implements Factory { public Car getCar() { return new Audi(); } }
public class BmwFactory implements Factory { public Car getCar() { return new Bmw(); } }
測試類如下:
public class FuncFactoryTest { public static void main(String[] args) { Car car1 = new AudiFactory().getCar(); System.out.println(car1.getName()); Car car2 = new BmwFactory().getCar(); System.out.println(car2.getName()); } }
抽象工廠(Abstract Factory)
上面的工廠模式生產的都是一類產品,而抽象工廠模式可以生產產品族。什麼是產品族呢?其實就是一組具有關聯關系的產品集合,舉幾個例子:
- 比如生產電腦,用Intel系列、AMD系列的零件(CPU、主板。。。。。),每個系列的零件就是一個產品族。
- 比如我們開發過程中會用到MySQL、Oracle數據庫,而不同的數據庫的操作會有不同,比如有User,Order兩個類,兩個類都有添加、修改操作,那User、Order的操作就要隨著數據庫的切換而切換,MySQL和Oracle下不同的類就組成瞭兩個產品族。
- 我們用QQ空間的時候會有換皮膚的功能,而每套皮膚下的背景、按鈕、導航、菜單。。。。。。這些也構成瞭產品族
- 我們上面用的生產汽車的例子,比如奧迪,寶馬不同品牌車的零部件(輪胎、發動機、軸承。。。。。。)
適用場景
- 和工廠方法一樣客戶端不需要知道它所創建的對象的類。
- 需要一組對象共同完成某種功能時,並且可能存在多組對象完成不同功能的情況。(同屬於同一個產品族的產品)
- 系統結構穩定,不會頻繁的增加對象。(因為一旦增加就需要修改原有代碼,不符合開閉原則)
角色分配
- 抽象工廠(AbstractFactory)角色 :是工廠方法模式的核心,與應用程序無關。任何在模式中創建的對象的工廠類必須實現這個接口。
- 具體工廠類(ConcreteFactory)角色 :這是實現抽象工廠接口的具體工廠類,包含與應用程序密切相關的邏輯,並且受到應用程序調用以創建某一種產品對象。
- 抽象產品(Abstract Product)角色 :工廠方法模式所創建的對象的超類型,也就是產品對象的共同父類或共同擁有的接口。
- 具體產品(Concrete Product)角色 :抽象工廠模式所創建的任何產品對象都是某一個具體產品類的實例。在抽象工廠中創建的產品屬於同一產品族,這不同於工廠模式中的工廠隻創建單一產品。
其UML類圖如下所示:
應用案例:
QQ空間換膚、更換數據庫等
優缺點:
- 優點:可以創建系列產品,方便切換使用的產品系列。
- 缺點:擴展產品族較為麻煩,不光要添加所有的抽象產品實現,還要添加產品族對應的工廠;另外添加產品也不簡單,要添加抽象產品,所有的產品族實現,還要對原先的工廠實現添加工廠方法以生產新產品。
抽象工廠實現
這個案例我們就以生產電腦為例,眾所周知目前電腦的CPU有兩大品牌:Intel和AMD。我們就以此為例,如果電腦選用Intel系列,就要用Intel的CPU和主板等,如果用AMD系列,就用AMD系列的零部件。
下面的兩個接口就是我們上面提到的抽象產品角色。
//CPU public interface CPU { public String getName(); }
//主板 public interface MainBoard { public String getName(); }
下面的四個類就是我們上面提到的具體產品角色。首先是Intel系列產品
public class IntelCPU implements CPU { public String getName() { return "IntelCPU"; } }
public class IntelMainBoard implements MainBoard{ public String getName() { return "IntelMainBoard"; } }
然後是AMD系列產品:
public class AMDCPU implements CPU { public String getName() { return "AMDCPU"; } }
public class AMDMainBoard implements MainBoard{ public String getName() { return "AMDMainBoard"; } }
下面的Factory就是抽象工廠角色,提供瞭生產CPU和主板的抽象方法。
public interface Factory { //生產CPU public CPU createCPU(); //生產主板 public MainBoard createMainBoard(); }
然後是具體的工廠類角色,分別生產不同系列的產品。
//Intel系列產品工廠 public class IntelFactory implements Factory { public CPU createCPU() { return new IntelCPU(); } public MainBoard createMainBoard() { return new IntelMainBoard(); } }
//AMD系列產品工廠 public class AMDFactory implements Factory { public CPU createCPU() { return new AMDCPU(); } public MainBoard createMainBoard() { return new AMDMainBoard(); } }
測試類如下:
public class FactoryTest { public static void main(String[] args) { Factory intel = new IntelFactory(); System.out.println(intel.createCPU().getName()); System.out.println(intel.createMainBoard().getName()); Factory amd = new AMDFactory(); System.out.println(amd.createCPU().getName()); System.out.println(amd.createMainBoard().getName()); } }
運行結果為:
IntelCPU
IntelMainBoard
AMDCPU
AMDMainBoard
抽象工廠終極改進(反射+配置文件+簡單工廠)
上面也說過抽象工廠的缺點是擴展產品族比較麻煩,我們對上面的抽象工廠做個改進,使其在添加產品族的時候更簡單一些。我們引入簡單工廠,但是簡單工廠擴展的時候要添加判斷條件,所以我們可以通過反射+配置文件去解決這個問題。改動後的UML圖如下所示:
改進後的代碼如下所示:Configuration類用於讀取配置文件(沒有具體去實現)
type=Intel packageName=com.wkp.design.pattern.factory.abst
public class SimpleFactory { static class Configuration{ public static String get(String key){ String value="";//TODO 讀取配置文件得到value return value; } } //通過配置文件讀取產品族類型及包名 private static final String type=Configuration.get("type"); private static final String packageName=Configuration.get("packageName"); //生產CPU public CPU createCPU() throws Exception{ return (CPU)Class.forName(packageName+"."+type+"CPU").newInstance(); } //生產主板 public MainBoard createMainBoard() throws Exception{ return (MainBoard)Class.forName(packageName+"."+type+"MainBoard").newInstance(); } }
測試代碼如下
public class SimpleFactoryTest { public static void main(String[] args) throws Exception { SimpleFactory factory = new SimpleFactory(); System.out.println(factory.createCPU().getName()); System.out.println(factory.createMainBoard().getName()); } }
我們看到,在調用的時候客戶端完全不用管用的是Intel還是AMD,這樣如果想切換產品族的話,隻需要修改配置文件即可,非常的方便。
到此這篇關於Java設計模式之簡單工廠 工廠方法 抽象工廠深度總結的文章就介紹到這瞭,更多相關Java 設計模式內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!