设计模式的征途—3.工厂方法(Factory Method)模式
上一篇的簡(jiǎn)單工廠模式雖然簡(jiǎn)單,但是存在一個(gè)很?chē)?yán)重的問(wèn)題:當(dāng)系統(tǒng)中需要引入新產(chǎn)品時(shí),由于靜態(tài)工廠方法通過(guò)所傳入?yún)?shù)的不同來(lái)創(chuàng)建不同的產(chǎn)品,這必定要修改工廠類(lèi)的源代碼,將違背開(kāi)閉原則。如何實(shí)現(xiàn)新增新產(chǎn)品而不影響已有代碼?工廠方法模式為此應(yīng)運(yùn)而生。
| 工廠方法模式(Factory Method) | 學(xué)習(xí)難度:★★☆☆☆ | 使用頻率:★★★★★ |
一、簡(jiǎn)單工廠版的日志記錄器
1.1 軟件需求說(shuō)明
Requirement:M公司欲開(kāi)發(fā)一個(gè)系統(tǒng)運(yùn)行日志記錄器(Logger),該記錄器可以通過(guò)多種途徑保存系統(tǒng)的運(yùn)行日志,例如通過(guò)文件記錄或數(shù)據(jù)庫(kù)記錄,用戶可以通過(guò)修改配置文件靈活地更換日志記錄方式。在設(shè)計(jì)各類(lèi)日志記錄器時(shí),M公司的開(kāi)發(fā)人員發(fā)現(xiàn)需要對(duì)日志記錄器進(jìn)行一些初始化工作,初始化參數(shù)的攝制過(guò)程比較復(fù)雜,而且某些參數(shù)的設(shè)置有嚴(yán)格的先后次序,否則可能會(huì)發(fā)生記錄失敗。如何封裝記錄器的初始化過(guò)程并保證多種記錄器切換的靈活性是M公司開(kāi)發(fā)人員面臨的一個(gè)難題。
M公司開(kāi)發(fā)人員學(xué)習(xí)了簡(jiǎn)單工廠模式對(duì)日志記錄器進(jìn)行了設(shè)計(jì),初始結(jié)構(gòu)如下圖所示。
1.2 基于簡(jiǎn)單工廠的代碼實(shí)現(xiàn)
M公司的程序猿按照結(jié)構(gòu)圖,寫(xiě)下了核心代碼LoggerFactory的CreateLogger方法:
// 簡(jiǎn)單工廠方法public static ILogger CreateLogger(string args){if (args.Equals("db", StringComparison.OrdinalIgnoreCase)){// 連接數(shù)據(jù)庫(kù),代碼省略// 創(chuàng)建數(shù)據(jù)庫(kù)日志記錄器對(duì)象ILogger logger = new DatabaseLogger();// 初始化數(shù)據(jù)庫(kù)日志記錄器,代碼省略return logger;}else if(args.Equals("file", StringComparison.OrdinalIgnoreCase)){// 創(chuàng)建日志文件,代碼省略// 創(chuàng)建文件日志記錄器對(duì)象ILogger logger = new FileLogger();// 初始化文件日志記錄器,代碼省略return logger;}else{return null;}}上述代碼省略了具體日志記錄器類(lèi)的初始化代碼,在LoggerFactory中提供了靜態(tài)工廠方法CreateLogger(),用于根據(jù)所傳入的參數(shù)創(chuàng)建各種不同類(lèi)型的日志記錄器。通過(guò)使用簡(jiǎn)單工廠模式,將日志記錄器對(duì)象的創(chuàng)建和使用分離,客戶端只需要使用由工廠類(lèi)創(chuàng)建的日志記錄器對(duì)象即可,無(wú)須關(guān)心對(duì)象的創(chuàng)建過(guò)程。
But,雖然簡(jiǎn)單工廠模式實(shí)現(xiàn)了對(duì)象的創(chuàng)建和使用分離,仍然存在以下兩個(gè)問(wèn)題:
(1)工廠類(lèi)過(guò)于龐大!包含了大量的if-else代碼,維護(hù)和測(cè)試的難度增大不少。
(2)系統(tǒng)擴(kuò)展不靈活,如果新增類(lèi)型的日志記錄器,必須修改靜態(tài)工廠方法的業(yè)務(wù)邏輯,違反了開(kāi)閉原則。
如何解決這兩個(gè)問(wèn)題,M公司程序猿苦思冥想,想要改進(jìn)簡(jiǎn)單工廠模式,于是開(kāi)始學(xué)習(xí)工廠方法模式。
二、工廠方法模式介紹
2.1 工廠方法模式概述
在簡(jiǎn)單工廠模式中只提供一個(gè)工廠類(lèi),該工廠類(lèi)需要知道每一個(gè)產(chǎn)品對(duì)象的創(chuàng)建細(xì)節(jié),并決定合適實(shí)例化哪一個(gè)產(chǎn)品類(lèi)。其最大的缺點(diǎn)就是當(dāng)有新產(chǎn)品加入時(shí),必須修改工廠類(lèi),需要在其中加入必要的業(yè)務(wù)邏輯,這違背了開(kāi)閉原則。此外,在簡(jiǎn)單工廠模式中,所有的產(chǎn)品都由同一個(gè)工廠創(chuàng)建,工廠類(lèi)職責(zé)較重,業(yè)務(wù)邏輯較為復(fù)雜,具體產(chǎn)品與工廠類(lèi)之間的耦合度較高,嚴(yán)重影響了系統(tǒng)的靈活性和擴(kuò)展性。
在工廠方法模式中,不再提供一個(gè)統(tǒng)一的工廠類(lèi)來(lái)創(chuàng)建所有的產(chǎn)品對(duì)象,而是針對(duì)不同的產(chǎn)品提供不同的工廠,系統(tǒng)提供一個(gè)與產(chǎn)品等級(jí)結(jié)構(gòu)對(duì)應(yīng)的工廠等級(jí)結(jié)構(gòu)。
工廠方法(Factory Method)模式:定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類(lèi)決定將哪一個(gè)類(lèi)實(shí)例化。工廠方法模式讓一個(gè)類(lèi)的實(shí)例化延遲到其子類(lèi)。工廠方法模式又簡(jiǎn)稱(chēng)為工廠模式,也可稱(chēng)為多態(tài)工廠模式,它是一種創(chuàng)建型模式?! ?/p>
2.2 工廠方法模式結(jié)構(gòu)圖
工廠方法模式提供一個(gè)抽象工廠接口來(lái)聲明抽象工廠方法,而由其子類(lèi)來(lái)具體實(shí)現(xiàn)工廠方法并創(chuàng)建具體的產(chǎn)品對(duì)象。
從圖中可以看出,在工廠方法模式結(jié)構(gòu)圖中包含以下4個(gè)角色:
(1)Product(抽象產(chǎn)品):定義產(chǎn)品的接口,是工廠方法模式所創(chuàng)建的對(duì)象的超類(lèi),也就是產(chǎn)品對(duì)象的公共父類(lèi)。
(2)ConcreteProduct(具體產(chǎn)品):它實(shí)現(xiàn)了抽象產(chǎn)品接口,某種類(lèi)型的具體產(chǎn)品由專(zhuān)門(mén)的具體工廠創(chuàng)建,具體工廠和具體產(chǎn)品之間一一對(duì)應(yīng)。
(3)Factory(抽象工廠):抽象工廠類(lèi),聲明了工廠方法,用于返回一個(gè)產(chǎn)品。
(4)ConcreteFactory(具體工廠):抽象工廠的子類(lèi),實(shí)現(xiàn)了抽象工廠中定義的工廠方法,并可由客戶端調(diào)用,返回一個(gè)具體產(chǎn)品類(lèi)的實(shí)例。
三、工廠方法版的日志記錄器
3.1 解決方案
M公司的程序猿學(xué)習(xí)了工廠方法之后,決定使用工廠方法模式來(lái)重構(gòu)設(shè)計(jì),其基本結(jié)構(gòu)圖如下圖所示:
其中, Logger接口充當(dāng)抽象產(chǎn)品角色,而FileLogger和DatabaseLogger則充當(dāng)具體產(chǎn)品角色。LoggerFactory接口充當(dāng)抽象工廠角色,而FileLoggerFactory和DatabaseLoggerFactory則充當(dāng)具體工廠角色。
3.2 重構(gòu)代碼
(1)抽象產(chǎn)品:ILogger接口
public interface ILogger{void WriteLog();}(2)具體產(chǎn)品:FileLogger和DatabaseLogger類(lèi)
public class FileLogger : ILogger{public void WriteLog(){Console.WriteLine("文件日志記錄...");}}public class DatabaseLogger : ILogger{public void WriteLog(){Console.WriteLine("數(shù)據(jù)庫(kù)日志記錄...");}}(3)抽象工廠:ILoggerFactory接口
public interface ILoggerFactory{ILogger CreateLogger();}(4)具體工廠:FileLoggerFactory和DatabaseLoggerFactory類(lèi)
public class FileLoggerFactory : ILoggerFactory{public ILogger CreateLogger(){// 創(chuàng)建文件日志記錄器ILogger logger = new FileLogger();// 創(chuàng)建文件,代碼省略return logger;}}public class DatabaseLoggerFactory : ILoggerFactory{public ILogger CreateLogger(){// 連接數(shù)據(jù)庫(kù),代碼省略// 創(chuàng)建數(shù)據(jù)庫(kù)日志記錄器對(duì)象ILogger logger = new DatabaseLogger();// 初始化數(shù)據(jù)庫(kù)日志記錄器,代碼省略return logger;}}(5)客戶端調(diào)用
public static void Main(){ILoggerFactory factory = new FileLoggerFactory(); // 可通過(guò)引入配置文件實(shí)現(xiàn)if (factory == null){return;}ILogger logger = factory.CreateLogger();logger.WriteLog();}運(yùn)行結(jié)果如下圖:
四、借助反射的重構(gòu)版本
4.1 逃離修改客戶端的折磨
為了讓系統(tǒng)具有更好的靈活性和可擴(kuò)展性,M公司程序猿決定對(duì)日志記錄器客戶端代碼進(jìn)行重構(gòu),使得可以在不修改任何客戶端代碼的基礎(chǔ)之上更換或是增加新的日志記錄方式。
在客戶端代碼中將不再使用new關(guān)鍵字來(lái)創(chuàng)建工廠對(duì)象,而是將具體工廠類(lèi)的類(lèi)名存在配置文件(例如XML文件)中,通過(guò)讀取配置文件來(lái)獲取類(lèi)名,再借助.NET反射機(jī)制來(lái)動(dòng)態(tài)地創(chuàng)建對(duì)象實(shí)例。
4.2 擼起袖子開(kāi)始重構(gòu)
(1)創(chuàng)建配置文件
<?xml version="1.0" encoding="utf-8" ?> <configuration><appSettings><add key="LoggerFactory" value="Manulife.ChengDu.DesignPattern.FactoryMethod.v2.DatabaseLoggerFactory, Manulife.ChengDu.DesignPattern.FactoryMethod" /></appSettings> </configuration>(2)封裝一個(gè)簡(jiǎn)單的AppConfigHelper類(lèi)
public class AppConfigHelper{public static string GetLoggerFactoryName(){string factoryName = null;try{factoryName = System.Configuration.ConfigurationManager.AppSettings["LoggerFactory"];}catch (Exception ex){Console.WriteLine(ex.Message);}return factoryName;}public static object GetLoggerFactoryInstance(){string assemblyName = AppConfigHelper.GetLoggerFactoryName();Type type = Type.GetType(assemblyName);var instance = Activator.CreateInstance(type);return instance;}}(2)重構(gòu)客戶端代碼
public static void Main(){ILoggerFactory factory = (ILoggerFactory)AppConfigHelper.GetLoggerFactoryInstance();if (factory == null){return;}ILogger logger = factory.CreateLogger();logger.WriteLog();}運(yùn)行結(jié)果如下圖所示:
五、工廠方法的隱藏
有時(shí)候,為了進(jìn)一步簡(jiǎn)化客戶端的使用,還可以對(duì)客戶端隱藏工廠方法,此時(shí),在工廠類(lèi)中將直接調(diào)用產(chǎn)品類(lèi)的業(yè)務(wù)方法,客戶端無(wú)須調(diào)用工廠方法創(chuàng)建產(chǎn)品,直接通過(guò)工廠即可使用所創(chuàng)建的對(duì)象中的業(yè)務(wù)方法。
(1)修改抽象工廠
public abstract class LoggerFactory{// 在工廠類(lèi)中直接調(diào)用日志記錄器的業(yè)務(wù)方法WriteLog()public void WriteLog(){ILogger logger = this.CreateLogger();logger.WriteLog();}public abstract ILogger CreateLogger();}(2)修改具體工廠
public class DatabaseLoggerFactory : LoggerFactory{public override ILogger CreateLogger(){// 連接數(shù)據(jù)庫(kù),代碼省略// 創(chuàng)建數(shù)據(jù)庫(kù)日志記錄器對(duì)象ILogger logger = new DatabaseLogger();// 初始化數(shù)據(jù)庫(kù)日志記錄器,代碼省略return logger;}}(3)簡(jiǎn)化的客戶端調(diào)用
public static void Main(){LoggerFactory factory = (LoggerFactory)AppConfigHelper.GetLoggerFactoryInstance();if (factory == null){return;}factory.WriteLog();}六、工廠方法模式總結(jié)
5.1 主要優(yōu)點(diǎn)
- 工廠方法用于創(chuàng)建客戶所需要的產(chǎn)品,還向客戶隱藏了哪種具體產(chǎn)品類(lèi)將被實(shí)例化這一細(xì)節(jié)。因此,用戶只需要關(guān)心所需產(chǎn)品對(duì)應(yīng)的工廠,無(wú)須關(guān)心創(chuàng)建細(xì)節(jié)。
- 在系統(tǒng)中加入新產(chǎn)品時(shí),無(wú)需修改抽象工廠和抽象產(chǎn)品提供的接口,也無(wú)須修改客戶端,還無(wú)須修改其他的具體工廠和具體產(chǎn)品,而只要加入一個(gè)具體工廠和具體產(chǎn)品就可以了。因此,系統(tǒng)的可擴(kuò)展性得到了保證,符合開(kāi)閉原則。
5.2 主要缺點(diǎn)
- 在添加新產(chǎn)品時(shí),需要編寫(xiě)新的具體產(chǎn)品類(lèi),還要提供與之對(duì)應(yīng)的具體工廠類(lèi),系統(tǒng)中類(lèi)的個(gè)數(shù)將成對(duì)增加,一定程度上增加了系統(tǒng)的復(fù)雜度。
- 由于考慮到系統(tǒng)的可擴(kuò)展性,需要引入抽象層,且在實(shí)現(xiàn)時(shí)可能需要用到反射等技術(shù),增加了系統(tǒng)的實(shí)現(xiàn)難度。
5.3 適用場(chǎng)景
- 客戶端不知道其所需要的對(duì)象的類(lèi)。在工廠方法模式中,客戶端不需要知道具體產(chǎn)品類(lèi)的類(lèi)名,只需要知道所對(duì)應(yīng)的的工廠即可,具體的產(chǎn)品對(duì)象由具體工廠創(chuàng)建,可將具體工廠的類(lèi)名存儲(chǔ)到配置文件或數(shù)據(jù)庫(kù)中。
- 抽象工廠類(lèi)通過(guò)其子類(lèi)來(lái)指定創(chuàng)建哪個(gè)對(duì)象。在工廠方法模式中,抽象工廠類(lèi)只需要提供一個(gè)創(chuàng)建產(chǎn)品的接口,而由其子類(lèi)來(lái)確定具體要?jiǎng)?chuàng)建的對(duì)象,利用面向?qū)ο蟮亩鄳B(tài)性和里氏替換原則,在程序運(yùn)行時(shí),子類(lèi)對(duì)象將覆蓋父類(lèi)對(duì)象,從而使得系統(tǒng)易于擴(kuò)展。
參考資料
? ? ??
劉偉,《設(shè)計(jì)模式的藝術(shù)—軟件開(kāi)發(fā)人員內(nèi)功修煉之道》
?
作者:周旭龍
出處:http://edisonchou.cnblogs.com
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文鏈接。
總結(jié)
以上是生活随笔為你收集整理的设计模式的征途—3.工厂方法(Factory Method)模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 碎屏险怎么理赔
- 下一篇: 前端面试题总结二(js原型继承)