概述我們都知道Java 中共有 23 種設(shè)計(jì)模式,其中工廠模式分為三種,即:簡(jiǎn)單工廠模式(不在 23 種設(shè)計(jì)模式之列)、工廠方法模式和抽象工廠模式;我們平時(shí)說的工廠模式,其實(shí)大都指工廠方法模式,這種模式是我們平時(shí)編碼中用的頻率最高的一種,在Spring 源碼中就有很多工廠模式的應(yīng)用,比如 BeanFactory 。
下面依次按照簡(jiǎn)單工廠模式、工廠方法模式、抽象工廠模式的順序,依次由淺入深說說這三種模式;文章分別從定義、場(chǎng)景、優(yōu)缺點(diǎn)也示例進(jìn)行講解。 簡(jiǎn)單工廠模式定義簡(jiǎn)單工廠模式(Simple Factory Pattern)是指由一個(gè)工廠對(duì)象決定創(chuàng)建出哪一種產(chǎn)品類的實(shí)例,簡(jiǎn)單來說就是, 定義一個(gè)工廠類,根據(jù)傳入的參數(shù)不同返回不同的實(shí)例,被創(chuàng)建的實(shí)例具有共同的父類或接口。 場(chǎng)景簡(jiǎn)單工廠適用于工廠類負(fù)責(zé)創(chuàng)建的對(duì)象較少的場(chǎng)景,且客戶端只需要傳入工廠類的參數(shù),對(duì)于如何創(chuàng)建對(duì)象的邏輯不需要關(guān)心??偨Y(jié)一下就是: 需要?jiǎng)?chuàng)建的對(duì)象較少; 客戶端不關(guān)心對(duì)象的創(chuàng)建過程;
優(yōu)缺點(diǎn)優(yōu)點(diǎn)實(shí)現(xiàn)了對(duì)責(zé)任的分割,提供了專門的工廠類用于創(chuàng)建對(duì)象 缺點(diǎn)工廠類的職責(zé)相對(duì)過重,不易于擴(kuò)展過于復(fù)雜的產(chǎn)品結(jié)構(gòu),不符合開閉原則(可解決) 示例接下來我們構(gòu)造一個(gè)場(chǎng)景來看看簡(jiǎn)單工廠模式的應(yīng)用:現(xiàn)在手機(jī)更新?lián)Q代的比較快,手機(jī)廠商每年基本都會(huì)在不同時(shí)間或者在同一時(shí)間發(fā)布生產(chǎn)不同型號(hào)和配置的手機(jī)。 假設(shè)某手機(jī)公司最近發(fā)布了型號(hào)為 A、B 的手機(jī),其中生產(chǎn)任務(wù)交給代工廠去生產(chǎn);我們都知道不管什么類型的手機(jī)都屬于手機(jī),所以我們先創(chuàng)建一個(gè)手機(jī)類Phone ,并在其中聲明一個(gè)公共的手機(jī)型號(hào)方法type : /**
* @author eamon.zhang
* @date 2019-09-27 上午10:55
*/
public interface Phone {
void type();
}
然后定義具體的手機(jī)類型: 型號(hào) A: /**
* @author eamon.zhang
* @date 2019-09-27 上午11:02
*/
public class PhoneA implements Phone {
@Override
public void type() {
System.out.println("型號(hào)為A的手機(jī)!");
}
}
型號(hào) B: /**
* @author eamon.zhang
* @date 2019-09-27 上午11:03
*/
public class PhoneB implements Phone {
@Override
public void type() {
System.out.println("型號(hào)為B的手機(jī)!");
}
}
創(chuàng)建手機(jī)代工廠 PhoneFactory 類: /**
* @author eamon.zhang
* @date 2019-09-27 上午10:54
*/
public class PhoneFactory {
public Phone product(String type) {
switch (type) {
case "A":
return new PhoneA();
case "B":
return new PhoneB();
default:
return null;
}
}
}
測(cè)試: /**
* @author eamon.zhang
* @date 2019-09-27 上午11:09
*/
public class PhoneFactoryTest {
@Test
public void product() {
PhoneFactory phoneFactory = new PhoneFactory();
phoneFactory.product("A").type();
phoneFactory.product("B").type();
}
}
輸出: 型號(hào)為A的手機(jī)!
型號(hào)為B的手機(jī)!
當(dāng)然,為了方便調(diào)用,PhoneFactory 中的product() 也可以寫成靜態(tài)的。
類圖: 
拓展解決不符合開閉原則問題上面的示例中,客戶端調(diào)用是簡(jiǎn)單了,但如果我們業(yè)務(wù)繼續(xù)擴(kuò)展,增加一個(gè)型號(hào) C,那么上面的工廠方法中的product() 方法就得再次修改邏輯。不符合開閉原則;因此我們客戶考慮對(duì)其進(jìn)行進(jìn)一步優(yōu)化,利用反射技術(shù)修改product() 方法: public Phone product(String className) {
try {
if (!(null == className || "".equals(className))) {
return (Phone) Class.forName(className).newInstance();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
修改客戶端調(diào)用代碼: public void product() {
PhoneFactory phoneFactory = new PhoneFactory();
phoneFactory.product("com.eamon.javadesignpatterns.factory.PhoneA").type();
phoneFactory.product("com.eamon.javadesignpatterns.factory.PhoneB").type();
}
經(jīng)過優(yōu)化之后,今后再增加型號(hào),就不用去修改工廠方法了;但是又有一個(gè)問題,方法參數(shù)是很長(zhǎng)的字符串,可控性有待提升,而且還需要強(qiáng)制轉(zhuǎn)型,不方便閱讀和維護(hù),所以進(jìn)一步改造: public Phone product(Class<? extends Phone> clazz) {
try {
if (null != clazz) {
return clazz.newInstance();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
優(yōu)化客戶端調(diào)用代碼: @Test
public void product() {
PhoneFactory phoneFactory = new PhoneFactory();
phoneFactory.product(PhoneA.class).type();
phoneFactory.product(PhoneB.class).type();
}
再來看一下類圖: 

其他簡(jiǎn)單工廠模式在 JDK 源碼中也無處不足,比如常用的 Calendar 類中Calendar.getInstance() 方法,跟進(jìn)源碼到createCalendar(TimeZone zone,Locale aLocale) 就可以看出。 還有就是 常用的logback ,我們可以看到 LoggerFactory 中有多個(gè)重載的方法 getLogger() : public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public final Logger getLogger(final Class<?> clazz) {
return getLogger(clazz.getName());
}
工廠方法模式定義工廠方法模式(Fatory Method Pattern)是指定義一個(gè)創(chuàng)建對(duì)象的接口,但讓實(shí)現(xiàn)這個(gè) 接口的類來決定實(shí)例化哪個(gè)類,工廠方法讓類的實(shí)例化推遲到子類中進(jìn)行。 在工廠方法模式中用戶只需要關(guān)心所需產(chǎn)品對(duì)應(yīng)的工廠,無須關(guān)心創(chuàng)建細(xì)節(jié),而且加入新的產(chǎn)品符 合開閉原則。 工廠方法模式主要解決產(chǎn)品擴(kuò)展的問題,在簡(jiǎn)單工廠中,隨著產(chǎn)品鏈的豐富,如果每個(gè)手機(jī)的創(chuàng)建邏輯有區(qū)別的話,工廠的職責(zé)會(huì)變得越來越多,有點(diǎn)像萬能工廠,并不便于維護(hù)。根據(jù)單一職責(zé)原則我們將職能繼續(xù)拆分,專人干專事。 場(chǎng)景工廠方法適用于以下場(chǎng)景: 創(chuàng)建對(duì)象需要大量重復(fù)的代碼。 客戶端(應(yīng)用層)不依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建、實(shí)現(xiàn)等細(xì)節(jié)。 一個(gè)類通過其子類來指定創(chuàng)建哪個(gè)對(duì)象。
優(yōu)缺點(diǎn)優(yōu)點(diǎn)具有良好的封裝性,代碼結(jié)構(gòu)清晰,井底了模塊間的耦合。 拓展性非常優(yōu)秀。(在增加產(chǎn)品類的情況下,只要修改具體的工廠類或擴(kuò)展一個(gè)工廠類) 屏蔽了產(chǎn)品類。(產(chǎn)品類的實(shí)現(xiàn)如何變化,調(diào)用者不需要關(guān)心)
缺點(diǎn):1、類的個(gè)數(shù)容易過多,增加復(fù)雜度。 2、增加了系統(tǒng)的抽象性和理解難度。 示例A 型號(hào)手機(jī)由PhoneA 工廠創(chuàng)建,B 型號(hào)手機(jī)由PhoneB 工廠創(chuàng)建,對(duì)工廠本身也做一個(gè)抽象。來看代碼,先創(chuàng)建 PhoneFactory 接口: /**
* @author eamon.zhang
* @date 2019-09-27 下午1:45
*/
public interface PhoneFactory {
Phone product();
}
分別創(chuàng)建子工廠 PhoneAFactory : /**
* @author eamon.zhang
* @date 2019-09-27 下午1:50
*/
public class PhoneAFactory implements PhoneFactory {
@Override
public Phone product() {
return new PhoneA();
}
}
PhoneBFactory 類:
/**
* @author eamon.zhang
* @date 2019-09-27 下午1:50
*/
public class PhoneBFactory implements PhoneFactory {
@Override
public Phone product() {
return new PhoneB();
}
}
看測(cè)試代碼: /**
* @author eamon.zhang
* @date 2019-09-27 下午1:54
*/
public class PhoneFactoryTest {
@Test
public void product() {
PhoneFactory factory = new PhoneAFactory();
factory.product().type();
factory = new PhoneBFactory();
factory.product().type();
}
}
測(cè)試結(jié)果: 型號(hào)為A的手機(jī)!
型號(hào)為B的手機(jī)!
再看一下類圖: 
拓展再來看看 logback 中工廠方法模式的應(yīng)用,看看類圖就 OK 了: 

抽象工廠模式定義抽象工廠模式(Abastract Factory Pattern)是指提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,無需指定他們具體的類。 客戶端(應(yīng)用層)不依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建、實(shí)現(xiàn)等細(xì)節(jié)。強(qiáng)調(diào)的是一系列相關(guān)的產(chǎn)品對(duì)象(屬于同一產(chǎn)品族)一起使用創(chuàng)建對(duì)象需要大量重復(fù)的代碼。需要提供一個(gè)產(chǎn)品類的庫(kù),所有的產(chǎn)品以同樣的接口出現(xiàn),從而使客戶端不依賴于具體實(shí)現(xiàn)。 理解為了便于大家理解抽象工廠,我們先了解兩個(gè)概念產(chǎn)品等級(jí)結(jié)構(gòu)和產(chǎn)品族,看下面的圖:
 從上圖中看出有正方形,圓形和三角形三種圖形,相同顏色深淺的就代表同一個(gè)產(chǎn)品族,相同形狀的代表同一個(gè)產(chǎn)品等級(jí)結(jié)構(gòu)。同樣可以從生活中來舉例,比如,美的電器生產(chǎn)多種家用電器。那么上圖中,顏色最深的正方形就代表美的洗衣機(jī)、顏色最深的圓形代表美的空調(diào)、顏色最深的三角形代表美的熱水器,顏色最深的一排都屬于美的品牌,都是美的電器這個(gè)產(chǎn)品族。再看最右側(cè)的三角形,顏色最深的我們指定了代表美的熱水器,那么第二排顏色稍微淺一點(diǎn)的三角形,代表海信的熱水器。同理,同一產(chǎn)品結(jié)構(gòu)下還有格力熱水器,格力空調(diào),格力洗衣機(jī)。 再看下面這張圖,最左側(cè)的箭頭代表具體的工廠,有美的工廠、海信工廠、格力工廠。每個(gè)品牌的工廠都生產(chǎn)洗衣機(jī)、熱水器、空調(diào)。 
通過上面兩張圖的對(duì)比理解,相信大家對(duì)抽象工廠有了非常形象的理解。 場(chǎng)景一個(gè)對(duì)象族(或是一組沒有任何關(guān)系的對(duì)象)都有相同的約束,則可以使用抽象工廠模式。簡(jiǎn)單來說: 和工廠方法一樣客戶端不需要知道它所創(chuàng)建的對(duì)象的類。 需要一組對(duì)象共同完成某種功能時(shí)。并且可能存在多組對(duì)象完成不同功能的情況。 系統(tǒng)結(jié)構(gòu)穩(wěn)定,不會(huì)頻繁的增加對(duì)象。(因?yàn)橐坏┰黾泳托枰薷脑写a,不符合開閉原則)
優(yōu)缺點(diǎn)優(yōu)點(diǎn)缺點(diǎn)示例比如現(xiàn)在有一個(gè)應(yīng)用,假如是某視頻軟件,需要在三個(gè)不同的平臺(tái)(Windows、IOS、Android)上運(yùn)行,該應(yīng)用針對(duì)每套系統(tǒng)都設(shè)計(jì)了一套上傳控制器(UploadController )、播放控制(DisplayController ),下面通過抽象工廠模式來設(shè)計(jì)該軟件。 視頻軟件里邊的各個(gè)平臺(tái)的UploadController 和DisplayController 應(yīng)該是我們最終生產(chǎn)的具體產(chǎn)品。所以新建兩個(gè)抽象產(chǎn)品接口。 UploadController 接口:
/**
* @author eamon.zhang
* @date 2019-09-27 下午2:59
*/
public interface UploadController {
void upload();
}
DisplayController 接口:
/**
* @author eamon.zhang
* @date 2019-09-27 下午2:59
*/
public interface DisplayController {
void display();
}
定義抽象工廠VideoPlayerFactory 類,它能夠創(chuàng)建UploadController 和DisplayController : /**
* 抽象工廠是主入口,在Spring中應(yīng)用的最廣泛的一種設(shè)計(jì)模式,易于擴(kuò)展
*
* @author eamon.zhang
* @date 2019-09-27 下午3:04
*/
public interface VideoPlayerFactory {
DisplayController createDisplayController();
UploadController createUploadController();
}
然后在各個(gè)平臺(tái)創(chuàng)建具體的 UploadController 和DisplayController : 創(chuàng)建適用于Windows的UploadController 和DisplayController : /**
* @author eamon.zhang
* @date 2019-09-27 下午3:09
*/
public class WindowsUploadController implements UploadController {
@Override
public void upload() {
System.out.println("Windows 上傳控制器!");
}
}
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:09
*/
public class WindowsDisplayController implements DisplayController {
@Override
public void display() {
System.out.println("Windows 上的播放器!");
}
}
創(chuàng)建適用于IOS的UploadController 和DisplayController : /**
* @author eamon.zhang
* @date 2019-09-27 下午3:10
*/
public class IosUploaderController implements UploadController {
@Override
public void upload() {
System.out.println("IOS 上傳控制器!");
}
}
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:09
*/
public class IosDisplayController implements DisplayController {
@Override
public void display() {
System.out.println("IOS 上的播放器!");
}
}
創(chuàng)建適用于Android的UploadController 和DisplayController : /**
* @author eamon.zhang
* @date 2019-09-27 下午3:10
*/
public class AndroidUploaderController implements UploadController {
@Override
public void upload() {
System.out.println("Android 上傳控制器!");
}
}
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:09
*/
public class AndroidDisplayController implements DisplayController {
@Override
public void display() {
System.out.println("Android 上的播放器!");
}
}
在各平臺(tái)具體的工廠類中完成上傳控制器和播放控制器的創(chuàng)建過程: 創(chuàng)建WindowsFactory 類: /**
* @author eamon.zhang
* @date 2019-09-27 下午3:15
*/
public class WindowsFactory implements VideoPlayerFactory {
@Override
public DisplayController createDisplayController() {
return new WindowsDisplayController();
}
@Override
public UploadController createUploadController() {
return new WindowsUploadController();
}
}
創(chuàng)建IosFactory 類: /**
* @author eamon.zhang
* @date 2019-09-27 下午3:17
*/
public class IosFactory implements VideoPlayerFactory {
@Override
public DisplayController createDisplayController() {
return new IosDisplayController();
}
@Override
public UploadController createUploadController() {
return new IosUploaderController();
}
}
創(chuàng)建AndroidFactory 類: /**
* @author eamon.zhang
* @date 2019-09-27 下午3:18
*/
public class AndroidFactory implements VideoPlayerFactory {
@Override
public DisplayController createDisplayController() {
return new AndroidDisplayController();
}
@Override
public UploadController createUploadController() {
return new AndroidUploaderController();
}
}
來看客戶端調(diào)用: /**
* @author eamon.zhang
* @date 2019-09-27 下午3:20
*/
public class VideoPlayerFactoryTest {
@Test
public void VideoPlayer() {
VideoPlayerFactory factory = new WindowsFactory();
// IOS
// factory = new IosFactory();
// // Android
// factory = new AndroidFactory();
UploadController uploadController = factory.createUploadController();
DisplayController displayController = factory.createDisplayController();
uploadController.upload();
displayController.display();
}
}
以調(diào)用 Windows 為例,結(jié)果: Windows 上傳控制器!
Windows 上的播放器!
上面就是針對(duì)不同平臺(tái)只通過創(chuàng)建對(duì)應(yīng)的工廠對(duì)象就完成了上傳控制器和播放控制器的創(chuàng)建。抽象工廠非常完美清晰地描述這樣一層復(fù)雜的關(guān)系。但是,不知道大家有沒有發(fā)現(xiàn),如果我們?cè)倮^續(xù)擴(kuò)展功能,將下載器也加入到產(chǎn)品中,那么我們的代碼從抽象工廠,到具體工廠要全部調(diào)整,很顯然不符合開閉原則。因此就有了上面優(yōu)缺點(diǎn)中所說的缺點(diǎn)。
總結(jié)在實(shí)際應(yīng)用中,我們千萬不能犯強(qiáng)迫癥甚至有潔癖。在實(shí)際需求中產(chǎn)品等級(jí)結(jié)構(gòu)升級(jí)是非常正常的一件事情。我們可以根據(jù)實(shí)際情況,只要不是頻繁升級(jí),可以不遵循開閉原則。代碼每半年升級(jí)一次或者每年升級(jí)一次又有何不可呢?
|