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

其他簡單工廠模式在 JDK 源碼中也無處不足,比如常用的 Calendar 類中Calendar.getInstance() 方法,跟進源碼到createCalendar(TimeZone zone,Locale aLocale) 就可以看出。 還有就是 常用的logback ,我們可以看到 LoggerFactory 中有多個重載的方法 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)是指定義一個創(chuàng)建對象的接口,但讓實現(xiàn)這個 接口的類來決定實例化哪個類,工廠方法讓類的實例化推遲到子類中進行。 在工廠方法模式中用戶只需要關心所需產(chǎn)品對應的工廠,無須關心創(chuàng)建細節(jié),而且加入新的產(chǎn)品符 合開閉原則。 工廠方法模式主要解決產(chǎn)品擴展的問題,在簡單工廠中,隨著產(chǎn)品鏈的豐富,如果每個手機的創(chuàng)建邏輯有區(qū)別的話,工廠的職責會變得越來越多,有點像萬能工廠,并不便于維護。根據(jù)單一職責原則我們將職能繼續(xù)拆分,專人干專事。 場景工廠方法適用于以下場景: 創(chuàng)建對象需要大量重復的代碼。 客戶端(應用層)不依賴于產(chǎn)品類實例如何被創(chuàng)建、實現(xiàn)等細節(jié)。 一個類通過其子類來指定創(chuàng)建哪個對象。
優(yōu)缺點優(yōu)點具有良好的封裝性,代碼結構清晰,井底了模塊間的耦合。 拓展性非常優(yōu)秀。(在增加產(chǎn)品類的情況下,只要修改具體的工廠類或擴展一個工廠類) 屏蔽了產(chǎn)品類。(產(chǎn)品類的實現(xiàn)如何變化,調用者不需要關心)
缺點:1、類的個數(shù)容易過多,增加復雜度。 2、增加了系統(tǒng)的抽象性和理解難度。 示例A 型號手機由PhoneA 工廠創(chuàng)建,B 型號手機由PhoneB 工廠創(chuàng)建,對工廠本身也做一個抽象。來看代碼,先創(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();
}
}
看測試代碼: /**
* @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();
}
}
測試結果: 型號為A的手機!
型號為B的手機!
再看一下類圖: 
拓展再來看看 logback 中工廠方法模式的應用,看看類圖就 OK 了: 

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