簡單工廠 + 工廠方法 + 抽象工廠
看了十幾篇博客,每篇基本上都能有個自己的解釋,我匯總這些內容,重新梳理整理了一番,以形成自己的理解。
簡單工廠模式其實不算23種設計模式之一,它是一個非常簡化版本的工廠。
這里只有一個工廠對象SimpleFactory,負責創建多個AbstractProduct類型具體產品實例。
public class SimpleFactory { public static void main(String[] args) { Car car = CarFactory.createCar("Audi"); car.setBrand("Audi"); //生產 car.produce(); } } abstract class Car{ private String brand; private String series;//暫時用不到 public abstract void produce(); public void setBrand(String brand) { this.brand = brand; } public void setSeries(String series) { this.series = series; } public String getBrand() { return brand; } public String getSeries() { return series; } } //具體產品 class Audi extends Car{ public void produce(){ System.out.println("produce : " + this.getBrand()); } } class Bmw extends Car{ public void produce(){ System.out.println("produce : " + this.getBrand()); } } //簡單工廠 class CarFactory { public static Car createCar(String car){ Car c = null; if("Audi".equalsIgnoreCase(car)) c = new Audi(); else if("Bmw".equalsIgnoreCase(car)) c = new Bmw(); return c; } }
在這個案例中,我要生產一輛車,不再需要自己new一個車,而只需要傳入一個類型(品牌),根據類型來創建車。的確隱藏了實現細節。但是問題在於:
使用者首先必須確定要create哪種車,然后傳入類型,然后在工廠里又if-else判斷了到底是哪種車,
違背了開閉原則,我需要增加對其他子類的支持,必須修改代碼,增加else判斷分支。
毫無疑問是有問題的,看起來這個if-else很多余,我既然已經告訴你我要奧迪車,你為什么內部自己還判斷了呢。當然,反射是可以避免if-else 的,但是要使用Class.forName() 必須要傳入類的完全限定名,這點在使用中是非常麻煩的。
如何解決?
既然我在工廠里面也要判斷哪種品牌,那為什么我不將判斷拿出來呢?對於奧迪車,我建立一個小工廠專門生產奧迪車,而奔馳、寶馬我也各自建一個工廠去生產。這樣,在客戶調用的時候,你只需要告訴我你准備用那個公司的生產車間,我就能生產這個品牌的汽車,這也就是工廠方法模式。
工廠方法模式,為了避免if-else判斷,干脆再封裝一層工廠
以此圖為例
client持有AbstractFactory的引用,AbstractFactory可創建具體工廠對象ConcreteFactory1,
ConcreteFactory1。
每個具體工廠可生產具體的產品 ConcreteProduct1,ConcreteProduct2。這兩個具體產品可抽象為AbstractProduct。因此用抽象父類接受具體子類對象。
public class FactoryMod { @Test public void test() { CarFactory factory = new AudiFactory(); factory.createCar().produce(); } abstract class Car{ private String brand; private String series;//暫時用不到 public abstract void produce(); public void setBrand(String brand) { this.brand = brand; } public void setSeries(String series) { this.series = series; } public String getBrand() { return brand; } public String getSeries() { return series; } } class Audi extends Car{ public void produce(){ System.out.println("produce : " + this.getBrand()); } } class Bmw extends Car{ public void produce(){ System.out.println("produce : " + this.getBrand()); } } class AudiFactory implements CarFactory{ public Car createCar(){ Car audi = new Audi(); audi.setBrand("audi"); return audi; } } class BmwFactory implements CarFactory{ public Car createCar(){ Car bmw = new Bmw(); bmw.setBrand("bmw"); return bmw; } } //工廠 interface CarFactory { Car createCar(); } }
此處我建了兩個工廠,分別生產不同品牌的汽車,這樣客戶端調用時,只要指明了是哪個工廠,就不必再有if-else判斷。如果我們增加奔馳生產的功能,只需要在Car下面增加奔馳類,並增加生產奔馳類的車間,無需改動代碼,就可以生成奔馳車。
而且我還可以指定生產什么型號的汽車:
class AudiFactory implements CarFactory{ public Car createCar(String series){ Car audi = new Audi(); audi.setBrand("audi"); audi.setSeries(series); return audi; } } class BmwFactory implements CarFactory{ public Car createCar(String series){ Car bmw = new Bmw(); bmw.setBrand("bmw"); bmw.setSeries(series); return bmw; } } //我指定汽車型號,客戶端想要生產汽車的時候必須告訴我型號,然后我就可以給你相應型號的汽車。 interface CarFactory { Car createCar(String series); }
上面的代碼就可以實現生產多個車型
但是這種寫法其實是有問題的,或者說適用性不強。因為這里我是吧車型當成了Car的一個內部屬性,但是實際上,車型可能是一個單獨的屬於奧迪車的子類。
那么如果我不僅要生產奧迪,我還要指定車型,比如奧迪A4,奧迪A6呢?工廠方法如何實現?
public class FactoryMod2 { @Test public void test() { CarFactory factory = new AudiFactory(); factory.createCar().produce();//生產奧迪汽車 factory.createCar("A4").produce();//生產奧都A4 } //汽車都有自己的序列號 abstract class Car { private String id = String.valueOf(Math.random()); public String getId() { return id; } public void setId(String id) { this.id = id; } public abstract void produce(); } //奧迪汽車還有自己的品牌名 class Audi extends Car { private String brand = "Audi"; public void produce() {
System.out.println("produce : " + this.getBrand() + "," + this.getId());
} public String getBrand() { return brand; } } //奧迪A4汽車除了品牌還有型號 class AudiA4 extends Audi { private String series = "A4"; public void produce() { System.out.println("produce : " + this.getBrand() + "," + this.getSeries() + "," + this.getId()); } public String getSeries() { return series; } } class AudiA6 extends Audi { private String series = "A6"; public void produce() { System.out.println("produce : " + this.getBrand() + "," + this.getSeries() + "," + this.getId()); } public String getSeries() { return series; } } class AudiFactory implements CarFactory { //要判斷客戶要什么系列 public Car createCar(String series) { Audi car = null; if (series.equals("A4")) return new AudiA4(); else if (series.equals("A6")) return new AudiA4(); return null; } public Car createCar(){ return new Audi(); } } //簡單工廠 interface CarFactory { Car createCar(String series); Car createCar(); } }
代碼稍微復雜了一點,這里我省略了寶馬工廠的創建,都是相似的,並且調整了繼承體系。
我們很容易在Audi下在派生子類AudiA4 和 AudiA6 沒問題,在用戶調用的時候我們要把用戶要求的型號告訴車間,車間里判斷用戶到底是要哪個系列,我們就給他哪個系列。很好,我們實現了功能:
produce :Audi,0.5005191840367693
produce :Audi,A4,0.1326089233983656
但是if-else的問題又來了,我們在奧迪生產車間里,又一次遇到了if判斷,判斷到底是哪種系列。就像一開始我們用簡單工廠模式一樣。
隨着需求的復雜度提高,單一的奧迪工廠已經無法輕松寫意的生產各種型號的奧迪汽車,它的produce方法開始負責A4和A6汽車的生產,
這違背了單一職責原則。進而違背了開閉原則。
怎么辦?
我們還是從剛才簡單工廠到工廠方法的轉變上,試着尋求消除if-else的方式,
前面在簡單工廠中,調用者已經指明我要的是奧迪汽車,但是工廠里還是判斷了一次,因為工廠意圖承擔多個創建責任。我們分析之后覺得應該把這個判斷拿出來。也就是說讓客戶直接選擇要創建哪種汽車,但是又不能直接new,所以我們就在原先工廠的地方再用一次工廠,將原先的職責划分開來,形成奧迪工廠和寶馬工廠,然后調用者直接拿奧迪工廠生產奧迪汽車,
現在因為需求變更,奧迪工廠不得不承擔多個生產責任,產生多個if-else。我們要干掉他,所以我們選擇在奧迪工廠上,仿照上面的做法,再用工廠划分出奧迪A4產品線,奧迪A6產品線。
我初步試了一下。
class AudiA4Factory implements CarFactory{ public Car createCar(){ return new AudiA4(); } } class AudiA6Factory implements CarFactory{ public Car createCar(){ return new AudiA6(); } }
發現這種基於垂直的繼承體系,最好的辦法就是直接使用父類工廠,直接創建子類,也就是說無論是奧迪還是奧迪A4,還是寶馬,都直接用對應的AudiA4Factory,AudiA6Factory來生產。既然客戶端知道確定的類型,就直接創建確定類型的工廠。當然這里看起來好像不太科學,如果垂直繼承體系很深,那么不同各層級的工廠有些不太清晰。關於這種垂直繼承體系,使用什么來創建比較好,此處不展開討論,或許直接創建子類的工廠,子類的子類的工廠更好,
如果要實現單一職責開閉原則的話,容易變化的就不能作為方法存在,比如生產各種型號的汽車,如果做成createAudiAt() createAudiA6()方法,就會出現之前的問題。
我們還有個問題沒有解決:抽象工廠怎么用?
抽象工廠的應用場景,不是類似於這種垂直的產品體系,因為這種垂直的體系就只有一個產品等級結構。
產品族與產品等級結構如圖所示。
產品等級結構就是一系列具有垂直繼承體系的產品。產品族就是一系列沒有繼承關系的產品集合,比如手槍和子彈,鼠標和鍵盤。
通過工廠方法與抽象工廠方法的區別來理解抽象工廠模式:
對於工廠方法:此處抽象產品類就是Car,派生的具體產品類就是Audi,Bmw。此處抽象工廠類就是CarFactory。具體工廠類就是AudiFactory.
根據前面的分析,抽象產品類可以派生多個具體產品類。抽象工廠類可以派生多個具體工廠類,而具體工廠類只能創建一個具體產品實例。多了就要違背開閉。
對於抽象工廠:是有多個抽象產品類的,也就是多個產品族,例子有:汽車、飛機、航母;槍與子彈,鼠標與鍵盤等。是不同繼承體系的產品。
多個抽象產品類可以抽象出多個具體產品類,比如雷蛇鼠標雷柏鼠標,鍵盤可能是雷蛇鍵盤雷柏鍵盤等。
每個抽象工廠,可以派生多個具體工廠類,而每個具體工廠類可以創建多個產品實例,意思就是每個工廠都能生產鼠標和鍵盤,但是卻有不同的工廠去生產不同的實例。
類圖如下:
現在有抽象工廠類AbstractFactory,它可以創建幾個具體的工廠 ConcreteFactory1,ConcreteFactory2,具體的每個工廠都能生產具體的A產品和B產品,但是A,B產品並沒有繼承關系,它們是不同的產品等級體系,現在要增加一個產品族,只需要增加一個相應產品族的工廠和具體的產品,比如A3,B3。大師要增加一個新產品比如C,那么3個工廠都需要修改內容,以生產新的產品。
代碼:
//抽象產品(Bmw和Audi同理) abstract class BenzCar{ private String name; public abstract void drive(); public String getName() { return name; } public void setName(String name) { this.name = name; } } //具體產品(Bmw和Audi同理) class BenzSportCar extends BenzCar{ public void drive(){ System.out.println(this.getName()+"----BenzSportCar-----------------------"); } } class BenzBusinessCar extends BenzCar{ public void drive(){ System.out.println(this.getName()+"----BenzBusinessCar-----------------------"); } } abstract class BmwCar{ private String name; public abstract void drive(); public String getName() { return name; } public void setName(String name) { this.name = name; } } class BmwSportCar extends BmwCar{ public void drive(){ System.out.println(this.getName()+"----BmwSportCar-----------------------"); } } class BmwBusinessCar extends BmwCar{ public void drive(){ System.out.println(this.getName()+"----BmwBusinessCar-----------------------"); } } abstract class AudiCar{ private String name; public abstract void drive(); public String getName() { return name; } public void setName(String name) { this.name = name; } } class AudiSportCar extends AudiCar{ public void drive(){ System.out.println(this.getName()+"----AudiSportCar-----------------------"); } } class AudiBusinessCar extends AudiCar{ public void drive(){ System.out.println(this.getName()+"----AudiBusinessCar-----------------------"); } } //抽象工廠 abstract class Driver3{ public abstract BenzCar createBenzCar(String car) throws Exception; public abstract BmwCar createBmwCar(String car) throws Exception; public abstract AudiCar createAudiCar(String car) throws Exception; } //具體工廠 class SportDriver extends Driver3{ public BenzCar createBenzCar(String car) throws Exception { return new BenzSportCar(); } public BmwCar createBmwCar(String car) throws Exception { return new BmwSportCar(); } public AudiCar createAudiCar(String car) throws Exception { return new AudiSportCar(); } } class BusinessDriver extends Driver3{ public BenzCar createBenzCar(String car) throws Exception { return new BenzBusinessCar(); } public BmwCar createBmwCar(String car) throws Exception { return new BmwBusinessCar(); } public AudiCar createAudiCar(String car) throws Exception { return new AudiBusinessCar(); } } //老板 public class BossAbstractFactory { public static void main(String[] args) throws Exception { Driver3 d = new BusinessDriver(); AudiCar car = d.createAudiCar(""); car.drive(); } }
上面的代碼比較清楚的展示了抽象方法的工作原理。
此處的抽象工廠是Driver3,具體工廠是BussinessDriver,SportDriver等。抽象產品是AudiCar,具體產品是AudiSportCar和AudiBussinessCar。
因此對於抽象工廠,支持多個產品族,要增加一個產品族很容易,只需要增加一個具體工廠,比如生產SUV型轎車。只需要實現一個SUVDriver,但是要增加一種汽車產品線,比如大眾。就必須在所有的產品族工廠里增加createVolkswagenCar,
如果用鼠標鍵盤舉例子的話,就是每個廠商(雷柏雷蛇賽睿)就是具體的工廠,是產品族。而每中產品,鼠標鍵盤就是產品等級體系。
有不清楚的可以繼續參考:
總結:
1.為什么用工廠方法?
概括起來大致有以下的說法:
將對象的實例化歸集起來,避免一個類到處實例化對象,一旦需求變更導致霰彈式修改,如果以后要改,直接在工廠里改就行了。
類的某些實例化需要有比較多的初始化設置,放到工廠里可以封裝代碼,而不至於到處都是重復的初始化代碼。
從解耦的角度考慮,不應該硬編碼,其實Spring才是最大的工廠,管理所有的代碼,實現所有代碼的解耦。springIOC有什么好處,工廠差不多也有這些好處。
將對象本身的職責,對象的創建創建邏輯,使用邏輯隔離開來。
-------------------------------------------------
也有認為工廠模式華而不實的。
也有認為工廠是C++時代設計模式遺留到java中的產物,其實並不一定需要,java通過反射可以方便的拿到所需要的實例。
不過普通程序員在java開發中廣泛運用spring框架,多數時候不太需要,而且對於javaWeb這種CRUD的項目來說,也不需要太多的設計模式。最后就是,許多人都沒有找到適合工廠模式的應用場景,不能為了模式而套模式,導致過度設計。工廠方法到底有沒有用,我還不能下定論,或許5年之后,我能理解這個模式更深層次的內涵。
2.何時使用工廠方法。
個人淺薄地認為,只要有new的地方,都可以考慮一下是否使用工廠,對於簡單很少有變動的情況,可以考慮用簡單的工廠,畢竟結構要清洗一點,也沒有特別大的變動。對於產品等級體系發達的情況,優先用工廠模式。對於產品族發達,而產品等級體系固定的情況,用抽象工廠。有些時候這幾種工廠可以組合使用,目的其實都是一樣,遵循6個設計原則,實現高內聚低耦合。
