設計模式系列——簡單工廠模式-Simple Factory Pattern

工廠模式是最常用的一類創建型設計模式,通常我們所說的工廠模式是指工廠方法模式,它也是使用頻率最高的工廠模式。本章將要學習的簡單工廠模式是工廠方法模式的“小弟”,它不屬于GoF 23種設計模式,但在軟件開發中應用也較為頻繁,通常將它作為學習其他工廠模式的入門。此外,工廠方法模式還有一位“大哥”——抽象工廠模式。這三種工廠模式各具特色,難度也逐個加大,在軟件開發中它們都得到了廣泛的應用,成為面向對象軟件中常用的創建對象的工具。

1 圖表庫的設計

Sunny軟件公司欲基于Java語言開發一套圖表庫,該圖表庫可以為應用系統提供各種不同外觀的圖表,例如柱狀圖、餅狀圖、折線圖等。Sunny軟件公司圖表庫設計人員希望為應用系統開發人員提供一套靈活易用的圖表庫,而且可以較為方便地對圖表庫進行擴展,以便能夠在將來增加一些新類型的圖表。

Sunny軟件公司圖表庫設計人員提出了一個初始設計方案,將所有圖表的實現代碼封裝在一個Chart類中,其框架代碼如下所示:

[java]view plaincopy

classChart?{

privateString?type;//圖表類型

publicChart(Object[][]?data,?String?type)?{

this.type?=?type;

if(type.equalsIgnoreCase("histogram"))?{

//初始化柱狀圖

}

elseif(type.equalsIgnoreCase("pie"))?{

//初始化餅狀圖

}

elseif(type.equalsIgnoreCase("line"))?{

//初始化折線圖

}

}

publicvoiddisplay()?{

if(this.type.equalsIgnoreCase("histogram"))?{

//顯示柱狀圖

}

elseif(this.type.equalsIgnoreCase("pie"))?{

//顯示餅狀圖

}

elseif(this.type.equalsIgnoreCase("line"))?{

//顯示折線圖

}

}

}

客戶端代碼通過調用Chart類的構造函數來創建圖表對象,根據參數type的不同可以得到不同類型的圖表,然后再調用display()方法來顯示相應的圖表。

不難看出,Chart類是一個“巨大的”類,在該類的設計中存在如下幾個問題:

(1)在Chart類中包含很多“if…else…”代碼塊,整個類的代碼相當冗長,代碼越長,閱讀難度、維護難度和測試難度也越大;而且大量條件語句的存在還將影響系統的性能,程序在執行過程中需要做大量的條件判斷。

(2) Chart類的職責過重,它負責初始化和顯示所有的圖表對象,將各種圖表對象的初始化代碼和顯示代碼集中在一個類中實現,違反了“單一職責原則”,不利于類的重用和維護;而且將大量的對象初始化代碼都寫在構造函數中將導致構造函數非常龐大,對象在創建時需要進行條件判斷,降低了對象創建的效率。

(3)當需要增加新類型的圖表時,必須修改Chart類的源代碼,違反了“開閉原則”。

(4)客戶端只能通過new關鍵字來直接創建Chart對象,Chart類與客戶端類耦合度較高,對象的創建和使用無法分離。

(5)客戶端在創建Chart對象之前可能還需要進行大量初始化設置,例如設置柱狀圖的顏色、高度等,如果在Chart類的構造函數中沒有提供一個默認設置,那就只能由客戶端來完成初始設置,這些代碼在每次創建Chart對象時都會出現,導致代碼的重復。

面對一個如此巨大、職責如此重,且與客戶端代碼耦合度非常高的類,我們應該怎么辦?本章將要介紹的簡單工廠模式將在一定程度上解決上述問題

2.簡單工廠模式概述

簡單工廠模式并不屬于GoF 23個經典設計模式,但通常將它作為學習其他工廠模式的基礎,它的設計思想很簡單,其基本流程如下:

首先將需要創建的各種不同對象(例如各種不同的Chart對象)的相關代碼封裝到不同的類中,這些類稱為具體產品類,而將它們公共的代碼進行抽象和提取后封裝在一個抽象產品類中,每一個具體產品類都是抽象產品類的子類;然后提供一個工廠類用于創建各種產品,在工廠類中提供一個創建產品的工廠方法,該方法可以根據所傳入的參數不同創建不同的具體產品對象;客戶端只需調用工廠類的工廠方法并傳入相應的參數即可得到一個產品對象。

簡單工廠模式定義如下:

簡單工廠模式(Simple Factory Pattern):定義一個工廠類,它可以根據參數的不同返回不同類的實例,被創建的實例通常都具有共同的父類。因為在簡單工廠模式中用于創建實例的方法是靜態(static)方法,因此簡單工廠模式又被稱為靜態工廠方法(Static Factory Method)模式,它屬于類創建型模式。

簡單工廠模式的要點在于:當你需要什么,只需要傳入一個正確的參數,就可以獲取你所需要的對象,而無須知道其創建細節。簡單工廠模式結構比較簡單,其核心是工廠類的設計,其結構如圖1所示:

圖1簡單工廠模式結構圖

在簡單工廠模式結構圖中包含如下幾個角色:

●Factory(工廠角色):工廠角色即工廠類,它是簡單工廠模式的核心,負責實現創建所有產品實例的內部邏輯;工廠類可以被外界直接調用,創建所需的產品對象;在工廠類中提供了靜態的工廠方法factoryMethod(),它的返回類型為抽象產品類型Product。

●Product(抽象產品角色):它是工廠類所創建的所有對象的父類,封裝了各種產品對象的公有方法,它的引入將提高系統的靈活性,使得在工廠類中只需定義一個通用的工廠方法,因為所有創建的具體產品對象都是其子類對象。

●ConcreteProduct(具體產品角色):它是簡單工廠模式的創建目標,所有被創建的對象都充當這個角色的某個具體類的實例。每一個具體產品角色都繼承了抽象產品角色,需要實現在抽象產品中聲明的抽象方法。

在簡單工廠模式中,客戶端通過工廠類來創建一個產品類的實例,而無須直接使用new關鍵字來創建對象,它是工廠模式家族中最簡單的一員。

在使用簡單工廠模式時,首先需要對產品類進行重構,不能設計一個包羅萬象的產品類,而需根據實際情況設計一個產品層次結構,將所有產品類公共的代碼移至抽象產品類,并在抽象產品類中聲明一些抽象方法,以供不同的具體產品類來實現,典型的抽象產品類代碼如下所示:

[java]view plaincopy

abstractclassProduct?{

//所有產品類的公共業務方法

publicvoidmethodSame()?{

//公共方法的實現

}

//聲明抽象業務方法

publicabstractvoidmethodDiff();

}

在具體產品類中實現了抽象產品類中聲明的抽象業務方法,不同的具體產品類可以提供不同的實現,典型的具體產品類代碼如下所示:

[java]view plaincopy

classConcreteProductextendsProduct?{

//實現業務方法

publicvoidmethodDiff()?{

//業務方法的實現

}

}

簡單工廠模式的核心是工廠類,在沒有工廠類之前,客戶端一般會使用new關鍵字來直接創建產品對象,而在引入工廠類之后,客戶端可以通過工廠類來創建產品,在簡單工廠模式中,工廠類提供了一個靜態工廠方法供客戶端使用,根據所傳入的參數不同可以創建不同的產品對象,典型的工廠類代碼如下所示:

[java]view plaincopy

classFactory?{

//靜態工廠方法

publicstaticProduct?getProduct(String?arg)?{

Product?product?=null;

if(arg.equalsIgnoreCase("A"))?{

product?=newConcreteProductA();

//初始化設置product

}

elseif(arg.equalsIgnoreCase("B"))?{

product?=newConcreteProductB();

//初始化設置product

}

returnproduct;

}

}

在客戶端代碼中,我們通過調用工廠類的工廠方法即可得到產品對象,典型代碼如下所示:

[java]view plaincopy

classClient?{

publicstaticvoidmain(String?args[])?{

Product?product;

product?=?Factory.getProduct("A");//通過工廠類創建產品對象

product.methodSame();

product.methodDiff();

}

}

3完整解決方案

為了將Chart類的職責分離,同時將Chart對象的創建和使用分離,Sunny軟件公司開發人員決定使用簡單工廠模式對圖表庫進行重構,重構后的結構如圖2所示:

圖2圖表庫結構圖

在圖2中,Chart接口充當抽象產品類,其子類HistogramChart、PieChart和LineChart充當具體產品類,ChartFactory充當工廠類。完整代碼如下所示:

[java]view plaincopy

//抽象圖表接口:抽象產品類

interfaceChart?{

publicvoiddisplay();

}

//柱狀圖類:具體產品類

classHistogramChartimplementsChart?{

publicHistogramChart()?{

System.out.println("創建柱狀圖!");

}

publicvoiddisplay()?{

System.out.println("顯示柱狀圖!");

}

}

//餅狀圖類:具體產品類

classPieChartimplementsChart?{

publicPieChart()?{

System.out.println("創建餅狀圖!");

}

publicvoiddisplay()?{

System.out.println("顯示餅狀圖!");

}

}

//折線圖類:具體產品類

classLineChartimplementsChart?{

publicLineChart()?{

System.out.println("創建折線圖!");

}

publicvoiddisplay()?{

System.out.println("顯示折線圖!");

}

}

//圖表工廠類:工廠類

classChartFactory?{

//靜態工廠方法

publicstaticChart?getChart(String?type)?{

Chart?chart?=null;

if(type.equalsIgnoreCase("histogram"))?{

chart?=newHistogramChart();

System.out.println("初始化設置柱狀圖!");

}

elseif(type.equalsIgnoreCase("pie"))?{

chart?=newPieChart();

System.out.println("初始化設置餅狀圖!");

}

elseif(type.equalsIgnoreCase("line"))?{

chart?=newLineChart();

System.out.println("初始化設置折線圖!");

}

returnchart;

}

}

編寫如下客戶端測試代碼:

[java]view plaincopy

classClient?{

publicstaticvoidmain(String?args[])?{

Chart?chart;

chart?=?ChartFactory.getChart("histogram");//通過靜態工廠方法創建產品

chart.display();

}

}

編譯并運行程序,輸出結果如下:

創建柱狀圖!

初始化設置柱狀圖!

顯示柱狀圖!

在客戶端測試類中,我們使用工廠類的靜態工廠方法創建產品對象,如果需要更換產品,只需修改靜態工廠方法中的參數即可,例如將柱狀圖改為餅狀圖,只需將代碼:

chart = ChartFactory.getChart("histogram");

改為:

chart = ChartFactory.getChart("pie");

編譯并運行程序,輸出結果如下:

創建餅狀圖!

初始化設置餅狀圖!

顯示餅狀圖!

4.方案的改進

Sunny軟件公司開發人員發現在創建具體Chart對象時,每更換一個Chart對象都需要修改客戶端代碼中靜態工廠方法的參數,客戶端代碼將要重新編譯,這對于客戶端而言,違反了“開閉原則”,有沒有一種方法能夠在不修改客戶端代碼的前提下更換具體產品對象呢?答案是肯定的,下面將介紹一種常用的實現方式。

我們可以將靜態工廠方法的參數存儲在XML或properties格式的配置文件中,如下config.xml所示:

[html]view plaincopy


histogram

再通過一個工具類XMLUtil來讀取配置文件中的字符串參數,XMLUtil類的代碼如下所示:

[java]view plaincopy

importjavax.xml.parsers.*;

importorg.w3c.dom.*;

importorg.xml.sax.SAXException;

importjava.io.*;

publicclassXMLUtil?{

//該方法用于從XML配置文件中提取圖表類型,并返回類型名

publicstaticString?getChartType()?{

try{

//創建文檔對象

DocumentBuilderFactory?dFactory?=?DocumentBuilderFactory.newInstance();

DocumentBuilder?builder?=?dFactory.newDocumentBuilder();

Document?doc;

doc?=?builder.parse(newFile("config.xml"));

//獲取包含圖表類型的文本節點

NodeList?nl?=?doc.getElementsByTagName("chartType");

Node?classNode?=?nl.item(0).getFirstChild();

String?chartType?=?classNode.getNodeValue().trim();

returnchartType;

}

catch(Exception?e)?{

e.printStackTrace();

returnnull;

}

}

}

在引入了配置文件和工具類XMLUtil之后,客戶端代碼修改如下:

[java]view plaincopy

classClient?{

publicstaticvoidmain(String?args[])?{

Chart?chart;

String?type?=?XMLUtil.getChartType();//讀取配置文件中的參數

chart?=?ChartFactory.getChart(type);//創建產品對象

chart.display();

}

}

不難發現,在上述客戶端代碼中不包含任何與具體圖表對象相關的信息,如果需要更換具體圖表對象,只需修改配置文件config.xml,無須修改任何源代碼,符合“開閉原則”。

思考

在簡單工廠模式中增加新的具體產品時是否符合“開閉原則”?如果不符合,原有系統需作出哪些修改?

5簡單工廠模式的簡化

有時候,為了簡化簡單工廠模式,我們可以將抽象產品類和工廠類合并,將靜態工廠方法移至抽象產品類中,如圖3所示:

圖3簡化的簡單工廠模式

在圖3中,客戶端可以通過產品父類的靜態工廠方法,根據參數的不同創建不同類型的產品子類對象,這種做法在JDK等類庫和框架中也廣泛存在。

6 簡單工廠模式總結

簡單工廠模式提供了專門的工廠類用于創建對象,將對象的創建和對象的使用分離開,它作為一種最簡單的工廠模式在軟件開發中得到了較為廣泛的應用。

1.主要優點

簡單工廠模式的主要優點如下:

(1)工廠類包含必要的判斷邏輯,可以決定在什么時候創建哪一個產品類的實例,客戶端可以免除直接創建產品對象的職責,而僅僅“消費”產品,簡單工廠模式實現了對象創建和使用的分離。

(2)客戶端無須知道所創建的具體產品類的類名,只需要知道具體產品類所對應的參數即可,對于一些復雜的類名,通過簡單工廠模式可以在一定程度減少使用者的記憶量。

(3)通過引入配置文件,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產品類,在一定程度上提高了系統的靈活性。

2.主要缺點

簡單工廠模式的主要缺點如下:

(1)由于工廠類集中了所有產品的創建邏輯,職責過重,一旦不能正常工作,整個系統都要受到影響。

(2)使用簡單工廠模式勢必會增加系統中類的個數(引入了新的工廠類),增加了系統的復雜度和理解難度。

(3)系統擴展困難,一旦添加新產品就不得不修改工廠邏輯,在產品類型較多時,有可能造成工廠邏輯過于復雜,不利于系統的擴展和維護。

(4)簡單工廠模式由于使用了靜態工廠方法,造成工廠角色無法形成基于繼承的等級結構。

3.適用場景

在以下情況下可以考慮使用簡單工廠模式:

(1)工廠類負責創建的對象比較少,由于創建的對象較少,不會造成工廠方法中的業務邏輯太過復雜。

(2)客戶端只知道傳入工廠類的參數,對于如何創建對象并不關心。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容