设计模式 之 工厂模式
項目源碼:https://gitee.com/Jacob-gitee/DesignMode
個人博客:https://jacob.org.cn
女媧造人的故事
東漢《風(fēng)俗通》記錄了一則神話故事:“開天辟地,未有人民,女媧搏黃土做人”,講述的內(nèi)容就是大家非常熟悉的女媧造人的故事。開天辟地之初,大地上并沒有生物,只有蒼茫大地,純粹而潔凈的自然環(huán)境,寂靜而又寂寞,于是女媧決定創(chuàng)造一個新物種(即人類)來增加世界的繁榮,怎么制造呢?
別忘了女媧是神仙,沒有辦不到的事情,造人的過程是這樣的:首先,女媧采集黃土捏成人的形狀,然后放到八卦爐中燒制,最后放置到大地上生長,工藝過程是沒有錯的,但是意外隨時都會發(fā)生:
第一次烤泥人,感覺應(yīng)該熟了,往大地上一放,哇,沒烤熟!于是一個白人誕生了!(這也是缺乏經(jīng)驗的最好證明。)第二次烤泥人,上一次沒烤熟,這次多烤一會兒,放到世間一看,嘿,熟過頭了,于是黑人誕生了!
第三次烤泥人,一邊燒制一邊察看,直到表皮微黃,嘿,剛剛好,于是黃色人種出現(xiàn)了!
這個造人過程是比較有意思的,是不是可以通過軟件開發(fā)來實現(xiàn)這個過程呢?古人 云:“三人行,必有我?guī)熝伞?#xff0c;在面向?qū)ο蟮乃季S中,萬物皆對象,是對象我們就可以通過軟件設(shè)計來實現(xiàn)。首先對造人過程進(jìn)行分析,該過程涉及三個對象:女媧、八卦爐、三種不同 膚色的人。女媧可以使用場景類Client來表示,八卦爐類似于一個工廠,負(fù)責(zé)制造生產(chǎn)產(chǎn)品(即人類),三種不同膚色的人,他們都是同一個接口下的不同實現(xiàn)類,都是人嘛,只是膚 色、語言不同,對于八卦爐來說都是它生產(chǎn)出的產(chǎn)品。分析完畢,我們就可以畫出如圖8-1所示的類圖。
類圖比較簡單,AbstractHumanFactory是一個抽象類,定義了一個八卦爐具有的整體功 能,HumanFactory為實現(xiàn)類,完成具體的任務(wù)——創(chuàng)建人類;Human接口是人類的總稱,其 三個實現(xiàn)類分別為三類人種;NvWa類是一個場景類,負(fù)責(zé)模擬這個場景,執(zhí)行相關(guān)的任務(wù)。
我們定義的每個人種都有兩個方法:getColor(獲得人的皮膚顏色)和talk(交談),其 源代碼如代碼清單8-1所示。
/*** @program: DesignMode* @description: 人類總稱 * @author: Jacob* @create: 2020-08-17 15:14**/ public interface Human {//每個人種的皮膚都有相應(yīng)的顏色public void getColor();//人類會說話public void talk();}接口Human是對人類的總稱,每個人種都至少具有兩個方法。
/*** @program: DesignMode* @description: 黑色人種* @author: Jacob* @create: 2020-08-17 15:15**/ public class BlackHuman implements Human {@Overridepublic void getColor() {System.out.println("黑色人種的皮膚顏色是黑色的!");}@Overridepublic void talk() {System.out.println("黑人會說話,一般人聽不懂。");} } /*** @program: DesignMode* @description: 黃色人種* @author: Jacob* @create: 2020-08-17 15:16**/ public class YellowHuman implements Human {@Overridepublic void getColor() {System.out.println("黃色人種的皮膚顏色是黃色的!");}@Overridepublic void talk() {System.out.println("黃色人種會說話,一般說的都是雙字節(jié)。");} } /*** @program: DesignMode* @description: 白色人種* @author: Jacob* @create: 2020-08-17 15:17**/ public class WhiteHuman implements Human {@Overridepublic void getColor() {System.out.println("白色人種的皮膚顏色是白色的!");}@Overridepublic void talk() {System.out.println("白色人種會說話,一般說的都是單字節(jié)。");}}所有的人種定義完畢,下一步就是定義一個八卦爐,然后燒制人類。我們想象一下,女 媧最可能給八卦爐下達(dá)什么樣的生產(chǎn)命令呢?應(yīng)該是”給我生產(chǎn)出一個黃色人種 (YellowHuman類)”,而不會是“給我生產(chǎn)一個會走、會跑、會說話、皮膚是黃色的人種”,因為這樣的命令增加了交流的成本,作為一個生產(chǎn)的管理者,只要知道生產(chǎn)什么就可以了,而不需要事物的具體信息。通過分析,我們發(fā)現(xiàn)八卦爐生產(chǎn)人類的方法輸入?yún)?shù)類型應(yīng)該是 Human接口的實現(xiàn)類,這也解釋了為什么類圖上的AbstractHumanFactory抽象類中createHuman 方法的參數(shù)為Class類型。
/*** @program: DesignMode* @description: 抽象人類創(chuàng)建工廠* @author: Jacob* @create: 2020-08-17 15:18**/ public abstract class AbstractHumanFactory {public abstract <T extends Human> T createHuman(Class<T> c);}注意,我們在這里采用了泛型(Generic),通過定義泛型對createHuman的輸入?yún)?shù)產(chǎn)生兩層限制:
● 必須是Class類型;
● 必須是Human的實現(xiàn)類。
其中的"T"表示的是,只要實現(xiàn)了Human接口的類都可以作為參數(shù),泛型是JDK 1.5中的 一個非常重要的新特性,它減少了對象間的轉(zhuǎn)換,約束其輸入?yún)?shù)類型,對Collection集合下 的實現(xiàn)類都可以定義泛型。
目前女媧只有一個八卦爐,其實現(xiàn)生產(chǎn)人類的方法。
/*** @program: DesignMode* @description: 人類創(chuàng)建工廠* @author: Jacob* @create: 2020-08-17 15:18**/ public class HumanFactory extends AbstractHumanFactory {@Overridepublic <T extends Human> T createHuman(Class<T> c) {Human human = null;try {human = (T)Class.forName(c.getName()).newInstance();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}return (T)human;} }人種有了,八卦爐也有了,剩下的工作就是女媧采集黃土,然后命令八卦爐開始生產(chǎn)
/*** @program: DesignMode* @description: 女媧類* @author: Jacob* @create: 2020-08-17 15:21**/ public class NvWa {public static void main(String[] args) {//聲明陰陽八卦爐AbstractHumanFactory YinYangLu = new HumanFactory();//女媧第一次造人,火候不足,于是白人產(chǎn)生了System.out.println("--造出的第一批人是白色人種--");Human whiteHuman = YinYangLu.createHuman(WhiteHuman.class);whiteHuman.getColor();whiteHuman.talk();//女媧第二次造人,火候過足,于是黑人產(chǎn)生了System.out.println("\n--造出的第二批人是黑色人種--");Human blackHuman = YinYangLu.createHuman(BlackHuman.class);blackHuman.getColor();blackHuman.talk();//第三次造人,火候剛剛好,于是黃色人種產(chǎn)生了System.out.println("\n--造出的第三批人是黃色人種--");Human yellowHuman = YinYangLu.createHuman(YellowHuman.class);yellowHuman.getColor();yellowHuman.talk();} }人種有了,八卦爐有了,負(fù)責(zé)生產(chǎn)的女媧也有了,激動人心的時刻到來了,我們運(yùn)行一下,結(jié)果如下所示。
--造出的第一批人是白色人種-- 白色人種的皮膚顏色是白色的! 白色人種會說話,一般說的都是單字節(jié)。--造出的第二批人是黑色人種-- 黑色人種的皮膚顏色是黑色的! 黑人會說話,一般人聽不懂。--造出的第三批人是黃色人種-- 黃色人種的皮膚顏色是黃色的! 黃色人種會說話,一般說的都是雙字節(jié)。哇,人類的生產(chǎn)過程就展現(xiàn)出來了!這個世界就熱鬧起來了,黑人、白人、黃人都開始活動了,這也正是我們現(xiàn)在的真實世界。以上就是工廠方法模式。
工廠方法模式的定義
工廠方法模式使用的頻率非常高,在我們?nèi)粘5拈_發(fā)中總能見到它的身影。其定義為:
Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定義一個用于創(chuàng)建對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。)
在工廠方法模式中,抽象產(chǎn)品類Product負(fù)責(zé)定義產(chǎn)品的共性,實現(xiàn)對事物最抽象的定 義;Creator為抽象創(chuàng)建類,也就是抽象工廠,具體如何創(chuàng)建產(chǎn)品類是由具體的實現(xiàn)工廠 ConcreteCreator完成的。工廠方法模式的變種較多,我們來看一個比較實用的通用源碼。
/*** @program: DesignMode* @description: 抽象產(chǎn)品類* @author: Jacob* @create: 2020-08-17 15:32**/ public abstract class Product {//產(chǎn)品類的公共方法public void method1() {//業(yè)務(wù)邏輯處理System.out.println("Product.method1");}//抽象方法public abstract void method2();}具體的產(chǎn)品類可以有多個,都繼承于抽象產(chǎn)品類。
/*** @program: DesignMode* @description: 具體產(chǎn)品類 1* @author: Jacob* @create: 2020-08-17 15:33**/ public class ConcreteProduct1 extends Product {@Overridepublic void method1() {//業(yè)務(wù)邏輯處理System.out.println("ConcreteProduct1.method1()");}@Overridepublic void method2() {//業(yè)務(wù)邏輯處理System.out.println("ConcreteProduct1.method2()");} } /*** @program: DesignMode* @description: 具體產(chǎn)品類 1* @author: Jacob* @create: 2020-08-17 15:33**/ public class ConcreteProduct2 extends Product {@Overridepublic void method2() {//業(yè)務(wù)邏輯處理System.out.println("ConcreteProduct2.method2()");} }抽象工廠類負(fù)責(zé)定義產(chǎn)品對象的產(chǎn)生。
/*** @program: DesignMode* @description: 抽象工廠類* @author: Jacob* @create: 2020-08-17 15:36**/ public abstract class Creator {/**創(chuàng)建一個產(chǎn)品對象,其輸入?yún)?shù)類型可以自行設(shè)置* 通常為String、Enum、Class等,當(dāng)然也可以為空*/public abstract <T extends Product> T createProduct(Class<T> c); }具體如何產(chǎn)生一個產(chǎn)品的對象,是由具體的工廠類實現(xiàn)的。
/*** @program: DesignMode* @description: 具體工廠類* @author: Jacob* @create: 2020-08-17 15:36**/ public class ConcreteCreator extends Creator {@Overridepublic <T extends Product> T createProduct(Class<T> c) {Product product = null;try {product = (T)Class.forName(c.getName()).newInstance();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}return (T)product;} } /*** @program: DesignMode* @description: 場景類* @author: Jacob* @create: 2020-08-17 15:38**/ public class Client {public static void main(String[] args) {Creator creator = new ConcreteCreator();Product product = creator.createProduct(ConcreteProduct1.class);product.method1();product.method2();product = creator.createProduct(ConcreteProduct2.class);product.method1();product.method2();}}我們運(yùn)行一下,結(jié)果如下所示。
ConcreteProduct1.method1() ConcreteProduct1.method2() Product.method1 ConcreteProduct2.method2()該通用代碼是一個比較實用、易擴(kuò)展的框架,讀者可以根據(jù)實際項目需要進(jìn)行擴(kuò)展。
工廠方法模式的應(yīng)用
工廠方法模式的優(yōu)點
首先,良好的封裝性,代碼結(jié)構(gòu)清晰。一個對象創(chuàng)建是有條件約束的,如一個調(diào)用者需要一個具體的產(chǎn)品對象,只要知道這個產(chǎn)品的類名(或約束字符串)就可以了,不用知道創(chuàng)建對象的艱辛過程,降低模塊間的耦合。
其次,工廠方法模式的擴(kuò)展性非常優(yōu)秀。在增加產(chǎn)品類的情況下,只要適當(dāng)?shù)匦薷木唧w 的工廠類或擴(kuò)展一個工廠類,就可以完成“擁抱變化”。例如在我們的例子中,需要增加一個 棕色人種,則只需要增加一個BrownHuman類,工廠類不用任何修改就可完成系統(tǒng)擴(kuò)展。
再次,屏蔽產(chǎn)品類。這一特點非常重要,產(chǎn)品類的實現(xiàn)如何變化,調(diào)用者都不需要關(guān)心,它只需要關(guān)心產(chǎn)品的接口,只要接口保持不變,系統(tǒng)中的上層模塊就不要發(fā)生變化。因為產(chǎn)品類的實例化工作是由工廠類負(fù)責(zé)的,一個產(chǎn)品對象具體由哪一個產(chǎn)品生成是由工廠類 決定的。在數(shù)據(jù)庫開發(fā)中,大家應(yīng)該能夠深刻體會到工廠方法模式的好處:如果使用JDBC 連接數(shù)據(jù)庫,數(shù)據(jù)庫從MySQL切換到Oracle,需要改動的地方就是切換一下驅(qū)動名稱(前提 條件是SQL語句是標(biāo)準(zhǔn)語句),其他的都不需要修改,這是工廠方法模式靈活性的一個直接案例。
最后,工廠方法模式是典型的解耦框架。高層模塊值需要知道產(chǎn)品的抽象類,其他的實現(xiàn)類都不用關(guān)心,符合迪米特法則,我不需要的就不要去交流;也符合依賴倒置原則,只依賴產(chǎn)品類的抽象;當(dāng)然也符合里氏替換原則,使用產(chǎn)品子類替換產(chǎn)品父類,沒問題!
工廠方法模式的使用場景
首先,工廠方法模式是new一個對象的替代品,所以在所有需要生成對象的地方都可以使用,但是需要慎重地考慮是否要增加一個工廠類進(jìn)行管理,增加代碼的復(fù)雜度。
其次,需要靈活的、可擴(kuò)展的框架時,可以考慮采用工廠方法模式。萬物皆對象,那萬物也就皆產(chǎn)品類,例如需要設(shè)計一個連接郵件服務(wù)器的框架,有三種網(wǎng)絡(luò)協(xié)議可供選擇: POP3、IMAP、HTTP,我們就可以把這三種連接方法作為產(chǎn)品類,定義一個接口如IConnectMail,然后定義對郵件的操作方法,用不同的方法實現(xiàn)三個具體的產(chǎn)品類(也就是連接方式)再定義一個工廠方法,按照不同的傳入條件,選擇不同的連接方式。如此設(shè)計, 可以做到完美的擴(kuò)展,如某些郵件服務(wù)器提供了WebService接口,很好,我們只要增加一個產(chǎn)品類就可以了。
再次,工廠方法模式可以用在異構(gòu)項目中,例如通過WebService與一個非Java的項目交 互,雖然WebService號稱是可以做到異構(gòu)系統(tǒng)的同構(gòu)化,但是在實際的開發(fā)中,還是會碰到 很多問題,如類型問題、WSDL文件的支持問題,等等。從WSDL中產(chǎn)生的對象都認(rèn)為是一個產(chǎn)品,然后由一個具體的工廠類進(jìn)行管理,減少與外圍系統(tǒng)的耦合。
最后,可以使用在測試驅(qū)動開發(fā)的框架下。例如,測試一個類A,就需要把與類A有關(guān) 聯(lián)關(guān)系的類B也同時產(chǎn)生出來,我們可以使用工廠方法模式把類B虛擬出來,避免類A與類B 的耦合。目前由于JMock和EasyMock的誕生,該使用場景已經(jīng)弱化了,讀者可以在遇到此種 情況時直接考慮使用JMock或EasyMock。
工廠方法模式的擴(kuò)展
縮小為簡單工廠模式
我們這樣考慮一個問題:一個模塊僅需要一個工廠類,沒有必要把它產(chǎn)生出來,使用靜 態(tài)的方法就可以了,根據(jù)這一要求,我們把上例中的AbstarctHumanFactory修改一下。
我們在類圖中去掉了AbstractHumanFactory抽象類,同時把createHuman方法設(shè)置為靜態(tài) 類型,簡化了類的創(chuàng)建過程,變更的源碼僅僅是HumanFactory和NvWa類,HumanFactory如代如下。
/*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--1. 縮小為簡單工廠模式 人類創(chuàng)建工廠* @author: Jacob* @create: 2020-08-17 15:18**/ public class HumanFactory {public static <T extends Human> T createHuman(Class<T> c) {Human human = null;try {human = (T)Class.forName(c.getName()).newInstance();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}return (T)human;} }HumanFactory類僅有兩個地方發(fā)生變化:去掉繼承抽象類,并在createHuman前增加static 關(guān)鍵字;工廠類發(fā)生變化,也同時引起了調(diào)用者NvWa的變化。
/*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--1. 縮小為簡單工廠模式 女媧類* @author: Jacob* @create: 2020-08-17 15:21**/ public class NvWa {public static void main(String[] args) {//女媧第一次造人,火候不足,于是白人產(chǎn)生了System.out.println("--造出的第一批人是白色人種--");Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class);whiteHuman.getColor();whiteHuman.talk();//女媧第二次造人,火候過足,于是黑人產(chǎn)生了System.out.println("\n--造出的第二批人是黑色人種--");Human blackHuman = HumanFactory.createHuman(BlackHuman.class);blackHuman.getColor();blackHuman.talk();//第三次造人,火候剛剛好,于是黃色人種產(chǎn)生了System.out.println("\n--造出的第三批人是黃色人種--");Human yellowHuman = HumanFactory.createHuman(YellowHuman.class);yellowHuman.getColor();yellowHuman.talk();} }運(yùn)行結(jié)果沒有發(fā)生變化,但是我們的類圖變簡單了,而且調(diào)用者也比較簡單,該模式是 工廠方法模式的弱化,因為簡單,所以稱為簡單工廠模式(Simple Factory Pattern),也叫做靜態(tài)工廠模式。在實際項目中,采用該方法的案例還是比較多的,其缺點是工廠類的擴(kuò)展比較困難,不符合開閉原則,但它仍然是一個非常實用的設(shè)計模式。
升級為多個工廠類
當(dāng)我們在做一個比較復(fù)雜的項目時,經(jīng)常會遇到初始化一個對象很耗費(fèi)精力的情況,所有的產(chǎn)品類都放到一個工廠方法中進(jìn)行初始化會使代碼結(jié)構(gòu)不清晰。例如,一個產(chǎn)品類有5 個具體實現(xiàn),每個實現(xiàn)類的初始化(不僅僅是new,初始化包括new一個對象,并對對象設(shè)
置一定的初始值)方法都不相同,如果寫在一個工廠方法中,勢必會導(dǎo)致該方法巨大無比,那該怎么辦?
考慮到需要結(jié)構(gòu)清晰,我們就為每個產(chǎn)品定義一個創(chuàng)造者,然后由調(diào)用者自己去選擇與哪個工廠方法關(guān)聯(lián)。我們還是以女媧造人為例,每個人種都有一個固定的八卦爐,分別造出 黑色人種、白色人種、黃色人種。
每個人種(具體的產(chǎn)品類)都對應(yīng)了一個創(chuàng)建者,每個創(chuàng)建者都獨(dú)立負(fù)責(zé)創(chuàng)建對應(yīng)的產(chǎn)品對象,非常符合單一職責(zé)原則,按照這種模式我們來看看代碼變化。
/*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--2. 升級為多個工廠類 多工廠模式的抽象工廠類* @author: Jacob* @create: 2020-08-17 15:18**/ public abstract class AbstractHumanFactory {public abstract Human createHuman();}注意 抽象方法中已經(jīng)不再需要傳遞相關(guān)參數(shù)了,因為每一個具體的工廠都已經(jīng)非常明確自己的職責(zé):創(chuàng)建自己負(fù)責(zé)的產(chǎn)品類對象。
/*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--2. 升級為多個工廠類 黑色人種的創(chuàng)建工廠實現(xiàn)* @author: Jacob* @create: 2020-08-17 16:06**/ public class BlackHumanFactory extends AbstractHumanFactory {@Overridepublic Human createHuman() {return new BlackHuman();} } /*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--2. 升級為多個工廠類 白色人種的創(chuàng)建類* @author: Jacob* @create: 2020-08-17 16:05**/ public class whiteHumanFactory extends AbstractHumanFactory {@Overridepublic Human createHuman() {return new WhiteHuman();} } /*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--2. 升級為多個工廠類 黃色人種的創(chuàng)建類* @author: Jacob* @create: 2020-08-17 16:02**/ public class YellowHumanFactory extends AbstractHumanFactory {@Overridepublic Human createHuman() {return new YellowHuman();} }三個具體的創(chuàng)建工廠都非常簡單,但是,如果一個系統(tǒng)比較復(fù)雜時工廠類也會相應(yīng)地變 復(fù)雜。場景類NvWa修改后的代碼如下:
/*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--2. 升級為多個工廠類 女媧類* @author: Jacob* @create: 2020-08-17 15:21**/ public class NvWa {public static void main(String[] args) {//女媧第一次造人,火候不足,于是白人產(chǎn)生了System.out.println("--造出的第一批人是白色人種--");Human whiteHuman = new whiteHumanFactory().createHuman();whiteHuman.getColor();whiteHuman.talk();//女媧第二次造人,火候過足,于是黑人產(chǎn)生了System.out.println("\n--造出的第二批人是黑色人種--");Human blackHuman = new BlackHumanFactory().createHuman();blackHuman.getColor();blackHuman.talk();//第三次造人,火候剛剛好,于是黃色人種產(chǎn)生了System.out.println("\n--造出的第三批人是黃色人種--");Human yellowHuman = new YellowHumanFactory().createHuman();yellowHuman.getColor();yellowHuman.talk();} }運(yùn)行結(jié)果還是相同。我們回顧一下,每一個產(chǎn)品類都對應(yīng)了一個創(chuàng)建類,好處就是創(chuàng)建類的職責(zé)清晰,而且結(jié)構(gòu)簡單,但是給可擴(kuò)展性和可維護(hù)性帶來了一定的影響。為什么這么說呢?如果要擴(kuò)展一個產(chǎn)品類,就需要建立一個相應(yīng)的工廠類,這樣就增加了擴(kuò)展的難度。因為工廠類和產(chǎn)品類的數(shù)量相同,維護(hù)時需要考慮兩個對象之間的關(guān)系。
當(dāng)然,在復(fù)雜的應(yīng)用中一般采用多工廠的方法,然后再增加一個協(xié)調(diào)類,避免調(diào)用者與各個子工廠交流,協(xié)調(diào)類的作用是封裝子工廠類,對高層模塊提供統(tǒng)一的訪問接口。
替代單例模式
之前講的單例模式以及擴(kuò)展出的多例模式,并且指出了單例和多例的一些缺點,我們是不是可以采用工廠方法模式實現(xiàn)單例模式的功能呢?單例模式的核心要求就是在內(nèi)存中 只有一個對象,通過工廠方法模式也可以只在內(nèi)存中生產(chǎn)一個對象。
非常簡單的類圖,Singleton定義了一個private的無參構(gòu)造函數(shù),目的是不允許通過new的 方式創(chuàng)建一個對象。
/*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--3. 替代單例模式 單例類* @author: Jacob* @create: 2020-08-17 16:15**/ public class Singleton {//不允許通過new產(chǎn)生一個對象private Singleton() {}public void doSomething() {//業(yè)務(wù)處理} }Singleton保證不能通過正常的渠道建立一個對象,那SingletonFactory如何建立一個單例 對象呢?答案是通過反射方式創(chuàng)建.。
/*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--3. 替代單例模式 單例工程* @author: Jacob* @create: 2020-08-17 16:21**/ public class SingletonFactory {private static Singleton singleton;static {try {Class cl = Class.forName(Singleton.class.getName());//獲得無參構(gòu)造Constructor constructor = cl.getDeclaredConstructor();//設(shè)置無參構(gòu)造是可訪問的constructor.setAccessible(true);//產(chǎn)生一個實例對象singleton = (Singleton) constructor.newInstance();} catch (Exception e) {//異常處理}}public static Singleton getSingleton() {return singleton;} }通過獲得類構(gòu)造器,然后設(shè)置訪問權(quán)限,生成一個對象,然后提供外部訪問,保證內(nèi)存中的對象唯一。當(dāng)然,其他類也可以通過反射的方式建立一個單例對象,確實如此,但是一個項目或團(tuán)隊是有章程和規(guī)范的,何況已經(jīng)提供了一個獲得單例對象的方法,為什么還要重
新創(chuàng)建一個新對象呢?除非是有人作惡。
以上通過工廠方法模式創(chuàng)建了一個單例對象,該框架可以繼續(xù)擴(kuò)展,在一個項目中可以 產(chǎn)生一個單例構(gòu)造器,所有需要產(chǎn)生單例的類都遵循一定的規(guī)則(構(gòu)造方法是private),然后通過擴(kuò)展該框架,只要輸入一個類型就可以獲得唯一的一個實例。
延遲初始化
何為延遲初始化(Lazy initialization)?一個對象被消費(fèi)完畢后,并不立刻釋放,工廠類保持其初始狀態(tài),等待再次被使用。延遲初始化是工廠方法模式的一個擴(kuò)展應(yīng)用。
ProductFactory負(fù)責(zé)產(chǎn)品類對象的創(chuàng)建工作,并且通過prMap變量產(chǎn)生一個緩存,對需要 再次被重用的對象保留,Product和ConcreteProduct是一個示例代碼。
/*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--4. 延遲初始化 抽象產(chǎn)品類* @author: Jacob* @create: 2020-08-17 15:32**/ public abstract class Product {//產(chǎn)品類的公共方法public void method1() {//業(yè)務(wù)邏輯處理System.out.println("Product.method1");}//抽象方法public abstract void method2();} /*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--4. 延遲初始化 具體產(chǎn)品類 1* @author: Jacob* @create: 2020-08-17 15:33**/ public class Product1 extends Product {@Overridepublic void method1() {//業(yè)務(wù)邏輯處理System.out.println("ConcreteProduct1.method1()");}@Overridepublic void method2() {//業(yè)務(wù)邏輯處理System.out.println("ConcreteProduct1.method2()");} } /*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--4. 延遲初始化 具體產(chǎn)品類 1* @author: Jacob* @create: 2020-08-17 15:33**/ public class Product2 extends Product {@Overridepublic void method2() {//業(yè)務(wù)邏輯處理System.out.println("ConcreteProduct2.method2()");} } /*** @program: DesignMode* @description: 具體工廠類* @author: Jacob* @create: 2020-08-17 15:36**/ public class ProductFactory {private static final Map<String, Product> prMap = new HashMap<>();public static synchronized Product createProduct(String type) {Product product = null;if (prMap.containsKey(type)) {product = prMap.get(type);System.out.println("Map中已經(jīng)有這個對象");} else {switch (type) {case "product1":product = new Product1();break;case "product2":product = new Product2();break;default:throw new RuntimeException("創(chuàng)建對象異常");}prMap.put(type, product);}return product;} } /*** @program: DesignMode* @description: 工廠方法模式的擴(kuò)展--4. 延遲初始化 場景類* @author: Jacob* @create: 2020-08-17 15:38**/ public class Client {public static void main(String[] args) {Product product = ProductFactory.createProduct("product1");product.method1();product.method2();System.out.println("---------------------------------------------");product = ProductFactory.createProduct("product2");product.method1();product.method2();System.out.println("---------------------------------------------");product = ProductFactory.createProduct("product1");product.method1();product.method2();}}代碼還比較簡單,通過定義一個Map容器,容納所有產(chǎn)生的對象,如果在Map容器中已 經(jīng)有的對象,則直接取出返回;如果沒有,則根據(jù)需要的類型產(chǎn)生一個對象并放入到Map容器中,以方便下次調(diào)用。
延遲加載框架是可以擴(kuò)展的,例如限制某一個產(chǎn)品類的最大實例化數(shù)量,可以通過判斷 Map中已有的對象數(shù)量來實現(xiàn),這樣的處理是非常有意義的,例如JDBC連接數(shù)據(jù)庫,都會 要求設(shè)置一個MaxConnections最大連接數(shù)量,該數(shù)量就是內(nèi)存中最大實例化的數(shù)量。
延遲加載還可以用在對象初始化比較復(fù)雜的情況下,例如硬件訪問,涉及多方面的交互,則可以通過延遲加載降低對象的產(chǎn)生和銷毀帶來的復(fù)雜性。
最佳實踐
工廠方法模式在項目中使用得非常頻繁,以至于很多代碼中都包含工廠方法模式。該模式幾乎盡人皆知,但不是每個人都能用得好。熟能生巧,熟練掌握該模式,多思考工廠方法如何應(yīng)用,而且工廠方法模式還可以與其他模式混合使用(例如模板方法模式、單例模式、原型模式等),變化出無窮的優(yōu)秀設(shè)計,這也正是軟件設(shè)計和開發(fā)的樂趣所在。
學(xué)習(xí)于:《設(shè)計模式之禪》 — 秦小波
總結(jié)
以上是生活随笔為你收集整理的设计模式 之 工厂模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国际版(英文版)Skype使用国内卡打电
- 下一篇: 设计模式 之 单例模式