工廠模式是最常用的一種建立型模式,通常所說的工廠模式一般是指工廠方法模式。本篇是是工廠方法模式的“小弟”,我們可以將其理解為工廠方法模式的預備知識,它不屬於GoF 23種設計模式,但在軟體開發中卻也應用地比較頻繁。此外,工廠方法模式還有一位“大哥”—抽象工廠模式,會在後面進行介紹。
簡單工廠模式(Simple Factory) | 學習難度:★★☆☆☆ | 使用頻率:★★★☆☆ |
一、從一個圖表庫談起
M公司想要基於C#語言開發一套圖表庫,該圖表庫可以為應用系統提供各種不同外觀的圖示,例如柱狀圖、餅狀圖或折線圖等。M公司圖表庫設計開發人員希望為應用系統開發人員提供一套靈活易用的圖表庫,而且可以較為方便地對圖表庫進行擴充套件,以便於在將來增加一些新型別的圖表。
M公司的程式設計師提出了一個初始設計方案,將所有圖表的實現程式碼封裝在一個Chart類中,其框架程式碼如下所示:
public class Chart { private string type; // 圖表型別 public Chart(object[][] data, string type) { this.type = type; if (this.type.Equals("histogram", StringComparison.OrdinalIgnoreCase)) { // 初始化柱狀圖 } else if (this.type.Equals("pie", StringComparison.OrdinalIgnoreCase)) { // 初始化餅狀圖 } else if (this.type.Equals("line", StringComparison.OrdinalIgnoreCase)) { // 初始化折線圖 } } public void Display() { if (this.type.Equals("histogram", StringComparison.OrdinalIgnoreCase)) { // 顯示柱狀圖 } else if (this.type.Equals("pie", StringComparison.OrdinalIgnoreCase)) { // 顯示餅狀圖 } else if (this.type.Equals("line", StringComparison.OrdinalIgnoreCase)) { // 顯示折線圖 } } }
客戶端程式碼通過呼叫Chart類的建構函式來建立圖表物件,根據引數type的不同可以得到不同型別的圖示,然後再呼叫Display()方法來顯示相應的圖表。
但是,不難看出,Chart類是一個巨大的類,存在很多問題:
- 在Chart類中包含很多if-else程式碼塊,相當冗長,可讀性很差;
- Chart類的職責過重,負責初始化和顯示各種圖表物件,違反了單一職責原則;
- 當需要增加新的圖表型別時,必須修改Chart類的原始碼,違反了開閉原則;
- 客戶端只能通過new關鍵字來直接建立Chart物件,Chart類與客戶端類耦合度較高,物件的建立和使用無法分離;
- 客戶端在建立Chart物件之前可能還需要進行大量初始化設定,例如設定柱狀圖的顏色和高度等,如果在Chart類的建構函式中沒有提供一個預設設定,那就只能由客戶端來完成初始設定,這些程式碼在每次建立Chart物件時都會出現,導致程式碼的重複;
二、簡單工廠模式概述
2.1 要點
簡單工廠模式並不屬於GoF 23種經典設計模式,但通常將它作為學習其他工廠模式的基礎。
簡單工廠(Simple Factory)模式:定義一個工廠類,它可以根據引數的不同返回不同類的例項,被建立的例項通常都具有共同的父類。因為在簡單工廠模式中用於建立例項的方法是靜態(static)方法,因此簡單工廠模式又被稱為靜態工廠方法模式,它屬於建立型模式。
簡單工廠模式的要點在於:當你需要什麼,只需要傳入一個正確的引數,就可以獲取你所需的物件,而無須知道其建立細節。
2.2 結構圖
簡單工廠模式包含3個角色:
- Factory - 工廠角色:該模式的核心,負責實現建立所有產品例項的內部邏輯,提供一個靜態的工廠方法GetProduct(),返回抽象產品型別Product的例項。
- Product - 抽象產品角色:所有產品類的父類,封裝了各種產品物件的共有方法,它的引入將提高系統的靈活性,使得在工廠類中只需要定義一個通用的工廠方法,因為所有建立的具體產品物件都是其子類物件。
- ConcreteProduct - 具體產品角色:簡單工廠模式的建立目標,所有被建立的物件都充當這個角色的某個具體類的例項。
在簡單工廠模式中,客戶端通過工廠類來建立一個產品類的例項,而無須直接使用new關鍵字來建立物件。(可以看出,它是工廠模式家族中最簡單的一員)
三、重構圖表庫的實現
3.1 新的結構圖
為了將Chart類的職責分離,同時將Chart物件的建立和使用分離,M公司開發人員決定使用簡單工廠模式對圖表庫進行重構,重構後的結構圖如下所示:
3.2 新的程式碼實現
(1)抽象產品角色:IChartable介面
public interface IChartable { void Display(); }
(2)具體產品角色:各種圖表型別
public class HistogramChart : IChartable { public HistogramChart() { Console.WriteLine("建立柱狀圖..."); } public void Display() { Console.WriteLine("顯示柱狀圖..."); } } public class LineChart : IChartable { public LineChart() { Console.WriteLine("建立折線圖..."); } public void Display() { Console.WriteLine("顯示折線圖..."); } } public class PieChart : IChartable { public PieChart() { Console.WriteLine("建立餅狀圖..."); } public void Display() { Console.WriteLine("顯示餅狀圖..."); } }
(3)工廠角色:ChartFactory
public class ChartFactory { public static IChartable GetChart(string type) { IChartable chart = null; if (type.Equals("histogram", StringComparison.OrdinalIgnoreCase)) { chart = new HistogramChart(); Console.WriteLine("初始化設定柱狀圖..."); } else if (type.Equals("pie", StringComparison.OrdinalIgnoreCase)) { chart = new PieChart(); Console.WriteLine("初始化設定餅狀圖..."); } else if (type.Equals("line", StringComparison.OrdinalIgnoreCase)) { chart = new PieChart(); Console.WriteLine("初始化設定折線圖..."); } return chart; } }
(4)客戶端呼叫:
public static void Main() { IChartable chart = ChartFactory.GetChart("histogram"); if (chart != null) { chart.Display(); } chart = ChartFactory.GetChart("pie"); if (chart != null) { chart.Display(); } }
執行結果如下:
在客戶端程式碼中,使用工廠類的靜態方法來建立具體產品物件,如果需要更換產品,只需要修改靜態工廠方法中的引數即可。例如:將柱狀圖改為餅狀圖,只需要將程式碼:
IChartable chart = ChartFactory.GetChart("histogram");
改為:
IChartable chart = ChartFactory.GetChart("pie");
3.3 改進的方案
M公司開發人員發現在建立具體Chart物件時,每次更換一個Chart物件都需要修改客戶端中靜態工廠方法的引數,客戶端程式碼需要重新編譯,這對於客戶端而言,是違反了開閉原則的。於是,開發人員希望有一種方法能夠在不修改客戶端程式碼地前提下更換具體產品物件。
因此,他們考慮使用配置檔案(XML)來實現:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="charttype" value="histogram"/> </appSettings> </configuration>
客戶端因此改為:
public static void Main() { string type = AppConfigHelper.GetChartType(); // 讀取配置檔案中的charttype if (string.IsNullOrEmpty(type)) { return; } IChartable chart = ChartFactory.GetChart(type); if (chart != null) { chart.Display(); } }
執行結果如下:
四、簡單工廠模式總結
4.1 主要優點
- 實現了物件建立和使用的分離:客戶端可以免除直接建立產品物件的職責,而僅僅“消費”產品。
- 客戶端無須知道所建立的具體產品類的類名,只需要知道具體產品類所對應的的引數即可。
- 通過引入配置檔案,可以在不修改任何客戶端程式碼地情況下更換和增加新的具體產品類,在一定程度上提高了系統的靈活性。
4.2 主要缺點
- 由於工廠類集中了所有產品的建立邏輯,職責過重,一旦不能正常工作,整個系統都要受影響。
- 使用簡單工廠模式勢必會增加系統中類的個數(引入新的工廠類),增加了系統的複雜度和理解難度。
- 系統擴充套件困難,一旦新增新產品就不得不修改工廠邏輯,在產品型別較多時,有可能會造成工廠邏輯過於複雜,不利於系統的擴充套件和維護。
- 簡單工廠模式由於使用了靜態工廠方法,造成工廠角色無法形成基於繼承的等級結構。
4.3 適用場景
- 工廠類負責建立的物件比較少,由於建立的物件較少,不會造成工廠方法中的業務邏輯太過複雜。
- 客戶端只需要知道傳入工廠類的引數,對於如何建立物件並不關心。
參考資料
劉偉,《設計模式的藝術—軟體開發人員內功修煉之道》