【學(xué)習(xí)難度:★★☆☆☆肌蜻,使用頻率:★★★☆☆】
直接出處:簡單工廠模式
梳理和學(xué)習(xí):https://github.com/BruceOuyang/boy-design-pattern
簡書日期: 2018/03/05
簡書首頁:http://www.reibang.com/p/0fb891a7c5ed
工廠三兄弟之簡單工廠模式(一)
工廠模式是最常用的一類創(chuàng)建型設(shè)計(jì)模式浩嫌,通常我們所說的工廠模式是指工廠方法模式,它也是使用頻率最高的工廠模式。本章將要學(xué)習(xí)的簡單工廠模式是工廠方法模式的“小弟”阀蒂,它不屬于GoF 23種設(shè)計(jì)模式躲查,但在軟件開發(fā)中應(yīng)用也較為頻繁,通常將它作為學(xué)習(xí)其他工廠模式的入門荠察。此外置蜀,工廠方法模式還有一位“大哥”——抽象工廠模式。這三種工廠模式各具特色割粮,難度也逐個(gè)加大盾碗,在軟件開發(fā)中它們都得到了廣泛的應(yīng)用,成為面向?qū)ο筌浖谐S玫膭?chuàng)建對(duì)象的工具舀瓢。
1 圖表庫的設(shè)計(jì)
Sunny軟件公司欲基于Java語言開發(fā)一套圖表庫廷雅,該圖表庫可以為應(yīng)用系統(tǒng)提供各種不同外觀的圖表,例如柱狀圖京髓、餅狀圖航缀、折線圖等。Sunny軟件公司圖表庫設(shè)計(jì)人員希望為應(yīng)用系統(tǒng)開發(fā)人員提供一套靈活易用的圖表庫堰怨,而且可以較為方便地對(duì)圖表庫進(jìn)行擴(kuò)展芥玉,以便能夠在將來增加一些新類型的圖表。
Sunny軟件公司圖表庫設(shè)計(jì)人員提出了一個(gè)初始設(shè)計(jì)方案备图,將所有圖表的實(shí)現(xiàn)代碼封裝在一個(gè)Chart類中灿巧,其框架代碼如下所示:
class Chart {
private String type; //圖表類型
public Chart(Object[][] data, String type) {
this.type = type;
if (type.equalsIgnoreCase("histogram")) {
//初始化柱狀圖
}
else if (type.equalsIgnoreCase("pie")) {
//初始化餅狀圖
}
else if (type.equalsIgnoreCase("line")) {
//初始化折線圖
}
}
public void display() {
if (this.type.equalsIgnoreCase("histogram")) {
//顯示柱狀圖
}
else if (this.type.equalsIgnoreCase("pie")) {
//顯示餅狀圖
}
else if (this.type.equalsIgnoreCase("line")) {
//顯示折線圖
}
}
}
客戶端代碼通過調(diào)用Chart類的構(gòu)造函數(shù)來創(chuàng)建圖表對(duì)象赶袄,根據(jù)參數(shù)type的不同可以得到不同類型的圖表,然后再調(diào)用display()方法來顯示相應(yīng)的圖表抠藕。
不難看出饿肺,Chart類是一個(gè)“巨大的”類,在該類的設(shè)計(jì)中存在如下幾個(gè)問題:
(1) 在Chart類中包含很多“if…else…”代碼塊盾似,整個(gè)類的代碼相當(dāng)冗長敬辣,代碼越長,閱讀難度零院、維護(hù)難度和測試難度也越大溉跃;而且大量條件語句的存在還將影響系統(tǒng)的性能,程序在執(zhí)行過程中需要做大量的條件判斷告抄。
(2) Chart類的職責(zé)過重撰茎,它負(fù)責(zé)初始化和顯示所有的圖表對(duì)象,將各種圖表對(duì)象的初始化代碼和顯示代碼集中在一個(gè)類中實(shí)現(xiàn)玄妈,違反了“單一職責(zé)原則”乾吻,不利于類的重用和維護(hù);而且將大量的對(duì)象初始化代碼都寫在構(gòu)造函數(shù)中將導(dǎo)致構(gòu)造函數(shù)非常龐大拟蜻,對(duì)象在創(chuàng)建時(shí)需要進(jìn)行條件判斷绎签,降低了對(duì)象創(chuàng)建的效率。
(3) 當(dāng)需要增加新類型的圖表時(shí)酝锅,必須修改Chart類的源代碼诡必,違反了“開閉原則”。
(4) 客戶端只能通過new關(guān)鍵字來直接創(chuàng)建Chart對(duì)象搔扁,Chart類與客戶端類耦合度較高爸舒,對(duì)象的創(chuàng)建和使用無法分離。
(5) 客戶端在創(chuàng)建Chart對(duì)象之前可能還需要進(jìn)行大量初始化設(shè)置稿蹲,例如設(shè)置柱狀圖的顏色扭勉、高度等,如果在Chart類的構(gòu)造函數(shù)中沒有提供一個(gè)默認(rèn)設(shè)置苛聘,那就只能由客戶端來完成初始設(shè)置涂炎,這些代碼在每次創(chuàng)建Chart對(duì)象時(shí)都會(huì)出現(xiàn),導(dǎo)致代碼的重復(fù)设哗。
面對(duì)一個(gè)如此巨大唱捣、職責(zé)如此重,且與客戶端代碼耦合度非常高的類网梢,我們應(yīng)該怎么辦震缭?本章將要介紹的簡單工廠模式將在一定程度上解決上述問題。
為什么要引入工廠類战虏,大家可參見:創(chuàng)建對(duì)象與使用對(duì)象——談?wù)劰S的作用拣宰。
工廠三兄弟之簡單工廠模式(二)
2 簡單工廠模式概述
簡單工廠模式并不屬于GoF 23個(gè)經(jīng)典設(shè)計(jì)模式党涕,但通常將它作為學(xué)習(xí)其他工廠模式的基礎(chǔ),它的設(shè)計(jì)思想很簡單巡社,其基本流程如下:
首先將需要?jiǎng)?chuàng)建的各種不同對(duì)象(例如各種不同的Chart對(duì)象)的相關(guān)代碼封裝到不同的類中遣鼓,這些類稱為具體產(chǎn)品類,而將它們公共的代碼進(jìn)行抽象和提取后封裝在一個(gè)抽象產(chǎn)品類中重贺,每一個(gè)具體產(chǎn)品類都是抽象產(chǎn)品類的子類;然后提供一個(gè)工廠類用于創(chuàng)建各種產(chǎn)品回懦,在工廠類中提供一個(gè)創(chuàng)建產(chǎn)品的工廠方法气笙,該方法可以根據(jù)所傳入的參數(shù)不同創(chuàng)建不同的具體產(chǎn)品對(duì)象;客戶端只需調(diào)用工廠類的工廠方法并傳入相應(yīng)的參數(shù)即可得到一個(gè)產(chǎn)品對(duì)象怯晕。
簡單工廠模式定義如下:
簡單工廠模式(Simple Factory Pattern):定義一個(gè)工廠類潜圃,它可以根據(jù)參數(shù)的不同返回不同類的實(shí)例,被創(chuàng)建的實(shí)例通常都具有共同的父類舟茶。因?yàn)樵诤唵喂S模式中用于創(chuàng)建實(shí)例的方法是靜態(tài)(static)方法谭期,因此簡單工廠模式又被稱為靜態(tài)工廠方法(Static Factory Method)模式,它屬于類創(chuàng)建型模式吧凉。
簡單工廠模式的要點(diǎn)在于:當(dāng)你需要什么隧出,只需要傳入一個(gè)正確的參數(shù),就可以獲取你所需要的對(duì)象阀捅,而無須知道其創(chuàng)建細(xì)節(jié)胀瞪。簡單工廠模式結(jié)構(gòu)比較簡單,其核心是工廠類的設(shè)計(jì)饲鄙,其結(jié)構(gòu)如圖1所示:
在簡單工廠模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
Factory(工廠角色):工廠角色即工廠類凄诞,它是簡單工廠模式的核心,負(fù)責(zé)實(shí)現(xiàn)創(chuàng)建所有產(chǎn)品實(shí)例的內(nèi)部邏輯忍级;工廠類可以被外界直接調(diào)用帆谍,創(chuàng)建所需的產(chǎn)品對(duì)象;在工廠類中提供了靜態(tài)的工廠方法factoryMethod()轴咱,它的返回類型為抽象產(chǎn)品類型Product汛蝙。
Product(抽象產(chǎn)品角色):它是工廠類所創(chuàng)建的所有對(duì)象的父類,封裝了各種產(chǎn)品對(duì)象的公有方法嗦玖,它的引入將提高系統(tǒng)的靈活性患雇,使得在工廠類中只需定義一個(gè)通用的工廠方法,因?yàn)樗袆?chuàng)建的具體產(chǎn)品對(duì)象都是其子類對(duì)象宇挫。
ConcreteProduct(具體產(chǎn)品角色):它是簡單工廠模式的創(chuàng)建目標(biāo)苛吱,所有被創(chuàng)建的對(duì)象都充當(dāng)這個(gè)角色的某個(gè)具體類的實(shí)例。每一個(gè)具體產(chǎn)品角色都繼承了抽象產(chǎn)品角色器瘪,需要實(shí)現(xiàn)在抽象產(chǎn)品中聲明的抽象方法翠储。
在簡單工廠模式中绘雁,客戶端通過工廠類來創(chuàng)建一個(gè)產(chǎn)品類的實(shí)例,而無須直接使用new關(guān)鍵字來創(chuàng)建對(duì)象援所,它是工廠模式家族中最簡單的一員庐舟。
在使用簡單工廠模式時(shí),首先需要對(duì)產(chǎn)品類進(jìn)行重構(gòu)住拭,不能設(shè)計(jì)一個(gè)包羅萬象的產(chǎn)品類挪略,而需根據(jù)實(shí)際情況設(shè)計(jì)一個(gè)產(chǎn)品層次結(jié)構(gòu),將所有產(chǎn)品類公共的代碼移至抽象產(chǎn)品類滔岳,并在抽象產(chǎn)品類中聲明一些抽象方法杠娱,以供不同的具體產(chǎn)品類來實(shí)現(xiàn),典型的抽象產(chǎn)品類代碼如下所示:
abstract class Product {
//所有產(chǎn)品類的公共業(yè)務(wù)方法
public void methodSame() {
//公共方法的實(shí)現(xiàn)
}
//聲明抽象業(yè)務(wù)方法
public abstract void methodDiff();
}
在具體產(chǎn)品類中實(shí)現(xiàn)了抽象產(chǎn)品類中聲明的抽象業(yè)務(wù)方法谱煤,不同的具體產(chǎn)品類可以提供不同的實(shí)現(xiàn)摊求,典型的具體產(chǎn)品類代碼如下所示:
class ConcreteProduct extends Product {
//實(shí)現(xiàn)業(yè)務(wù)方法
public void methodDiff() {
//業(yè)務(wù)方法的實(shí)現(xiàn)
}
}
簡單工廠模式的核心是工廠類,在沒有工廠類之前刘离,客戶端一般會(huì)使用new關(guān)鍵字來直接創(chuàng)建產(chǎn)品對(duì)象室叉,而在引入工廠類之后,客戶端可以通過工廠類來創(chuàng)建產(chǎn)品硫惕,在簡單工廠模式中茧痕,工廠類提供了一個(gè)靜態(tài)工廠方法供客戶端使用,根據(jù)所傳入的參數(shù)不同可以創(chuàng)建不同的產(chǎn)品對(duì)象恼除,典型的工廠類代碼如下所示:
class Factory {
//靜態(tài)工廠方法
public static Product getProduct(String arg) {
Product abstractProduct = null;
if (arg.equalsIgnoreCase("A")) {
abstractProduct = new ConcreteProductA();
//初始化設(shè)置product
}
else if (arg.equalsIgnoreCase("B")) {
abstractProduct = new ConcreteProductB();
//初始化設(shè)置product
}
return abstractProduct;
}
}
在客戶端代碼中凿渊,我們通過調(diào)用工廠類的工廠方法即可得到產(chǎn)品對(duì)象,典型代碼如下所示:
class Client {
public static void main(String args[]) {
Product abstractProduct;
abstractProduct = Factory.getProduct("A"); //通過工廠類創(chuàng)建產(chǎn)品對(duì)象
abstractProduct.methodSame();
abstractProduct.methodDiff();
}
}
工廠三兄弟之簡單工廠模式(三)
3 完整解決方案
為了將Chart類的職責(zé)分離缚柳,同時(shí)將Chart對(duì)象的創(chuàng)建和使用分離埃脏,Sunny軟件公司開發(fā)人員決定使用簡單工廠模式對(duì)圖表庫進(jìn)行重構(gòu),重構(gòu)后的結(jié)構(gòu)如圖2所示:
在圖2中秋忙,Chart接口充當(dāng)抽象產(chǎn)品類彩掐,其子類HistogramChart、PieChart和LineChart充當(dāng)具體產(chǎn)品類灰追,ChartFactory充當(dāng)工廠類堵幽。完整代碼如下所示:
//抽象圖表接口:抽象產(chǎn)品類
interface Chart {
public void display();
}
//柱狀圖類:具體產(chǎn)品類
class HistogramChart implements Chart {
public HistogramChart() {
System.out.println("創(chuàng)建柱狀圖!");
}
public void display() {
System.out.println("顯示柱狀圖弹澎!");
}
}
//餅狀圖類:具體產(chǎn)品類
class PieChart implements Chart {
public PieChart() {
System.out.println("創(chuàng)建餅狀圖朴下!");
}
public void display() {
System.out.println("顯示餅狀圖!");
}
}
//折線圖類:具體產(chǎn)品類
class LineChart implements Chart {
public LineChart() {
System.out.println("創(chuàng)建折線圖苦蒿!");
}
public void display() {
System.out.println("顯示折線圖殴胧!");
}
}
//圖表工廠類:工廠類
class ChartFactory {
//靜態(tài)工廠方法
public static Chart getChart(String type) {
Chart chart = null;
if (type.equalsIgnoreCase("histogram")) {
chart = new HistogramChart();
System.out.println("初始化設(shè)置柱狀圖!");
}
else if (type.equalsIgnoreCase("pie")) {
chart = new PieChart();
System.out.println("初始化設(shè)置餅狀圖!");
}
else if (type.equalsIgnoreCase("line")) {
chart = new LineChart();
System.out.println("初始化設(shè)置折線圖团滥!");
}
return chart;
}
}
編寫如下客戶端測試代碼:
class Client {
public static void main(String args[]) {
Chart chart;
//通過靜態(tài)工廠方法創(chuàng)建產(chǎn)品
chart = ChartFactory.getChart("histogram");
chart.display();
}
}
編譯并運(yùn)行程序竿屹,輸出結(jié)果如下:
創(chuàng)建柱狀圖!
初始化設(shè)置柱狀圖灸姊!
顯示柱狀圖拱燃!
在客戶端測試類中,我們使用工廠類的靜態(tài)工廠方法創(chuàng)建產(chǎn)品對(duì)象力惯,如果需要更換產(chǎn)品碗誉,只需修改靜態(tài)工廠方法中的參數(shù)即可,例如將柱狀圖改為餅狀圖父晶,只需將代碼:
chart = ChartFactory.getChart("histogram");
改為
chart = ChartFactory.getChart("pie");
編譯并運(yùn)行程序诗充,輸出結(jié)果如下:
創(chuàng)建餅狀圖!
初始化設(shè)置餅狀圖诱建!
顯示餅狀圖!
工廠三兄弟之簡單工廠模式(四)
4 方案的改進(jìn)
Sunny軟件公司開發(fā)人員發(fā)現(xiàn)在創(chuàng)建具體Chart對(duì)象時(shí)碟绑,每更換一個(gè)Chart對(duì)象都需要修改客戶端代碼中靜態(tài)工廠方法的參數(shù)俺猿,客戶端代碼將要重新編譯,這對(duì)于客戶端而言格仲,違反了“開閉原則”押袍,有沒有一種方法能夠在不修改客戶端代碼的前提下更換具體產(chǎn)品對(duì)象呢?答案是肯定的凯肋,下面將介紹一種常用的實(shí)現(xiàn)方式谊惭。
我們可以將靜態(tài)工廠方法的參數(shù)存儲(chǔ)在XML或properties格式的配置文件中,如下config.xml所示:
<?xml version="1.0"?>
<config>
<chartType>histogram</chartType>
</config>
再通過一個(gè)工具類XMLUtil來讀取配置文件中的字符串參數(shù)侮东,XMLUtil類的代碼如下所示:
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
public class XMLUtil {
//該方法用于從XML配置文件中提取圖表類型圈盔,并返回類型名
public static String getChartType() {
try {
//創(chuàng)建文檔對(duì)象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("config.xml"));
//獲取包含圖表類型的文本節(jié)點(diǎn)
NodeList nl = doc.getElementsByTagName("chartType");
Node classNode = nl.item(0).getFirstChild();
String chartType = classNode.getNodeValue().trim();
return chartType;
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
在引入了配置文件和工具類XMLUtil之后,客戶端代碼修改如下:
class Client {
public static void main(String args[]) {
Chart chart;
//讀取配置文件中的參數(shù)
String type = XMLUtil.getChartType();
//創(chuàng)建產(chǎn)品對(duì)象
chart = ChartFactory.getChart(type);
chart.display();
}
}
不難發(fā)現(xiàn)悄雅,在上述客戶端代碼中不包含任何與具體圖表對(duì)象相關(guān)的信息驱敲,如果需要更換具體圖表對(duì)象,只需修改配置文件config.xml宽闲,無須修改任何源代碼众眨,符合“開閉原則”。
思考
在簡單工廠模式中增加新的具體產(chǎn)品時(shí)是否符合“開閉原則”容诬?如果不符合娩梨,原有系統(tǒng)需作出哪些修改?
5 簡單工廠模式的簡化
有時(shí)候览徒,為了簡化簡單工廠模式狈定,我們可以將抽象產(chǎn)品類和工廠類合并,將靜態(tài)工廠方法移至抽象產(chǎn)品類中习蓬,如圖3所示:
在圖3中掸冤,客戶端可以通過產(chǎn)品父類的靜態(tài)工廠方法厘托,根據(jù)參數(shù)的不同創(chuàng)建不同類型的產(chǎn)品子類對(duì)象,這種做法在JDK等類庫和框架中也廣泛存在稿湿。
6 簡單工廠模式總結(jié)
簡單工廠模式提供了專門的工廠類用于創(chuàng)建對(duì)象铅匹,將對(duì)象的創(chuàng)建和對(duì)象的使用分離開,它作為一種最簡單的工廠模式在軟件開發(fā)中得到了較為廣泛的應(yīng)用饺藤。
- 主要優(yōu)點(diǎn)
簡單工廠模式的主要優(yōu)點(diǎn)如下:
(1) 工廠類包含必要的判斷邏輯包斑,可以決定在什么時(shí)候創(chuàng)建哪一個(gè)產(chǎn)品類的實(shí)例,客戶端可以免除直接創(chuàng)建產(chǎn)品對(duì)象的職責(zé)涕俗,而僅僅“消費(fèi)”產(chǎn)品罗丰,簡單工廠模式實(shí)現(xiàn)了對(duì)象創(chuàng)建和使用的分離。
(2) 客戶端無須知道所創(chuàng)建的具體產(chǎn)品類的類名再姑,只需要知道具體產(chǎn)品類所對(duì)應(yīng)的參數(shù)即可萌抵,對(duì)于一些復(fù)雜的類名,通過簡單工廠模式可以在一定程度減少使用者的記憶量元镀。
(3) 通過引入配置文件绍填,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產(chǎn)品類,在一定程度上提高了系統(tǒng)的靈活性栖疑。
- 主要缺點(diǎn)
簡單工廠模式的主要缺點(diǎn)如下:
(1) 由于工廠類集中了所有產(chǎn)品的創(chuàng)建邏輯讨永,職責(zé)過重,一旦不能正常工作遇革,整個(gè)系統(tǒng)都要受到影響卿闹。
(2) 使用簡單工廠模式勢必會(huì)增加系統(tǒng)中類的個(gè)數(shù)(引入了新的工廠類),增加了系統(tǒng)的復(fù)雜度和理解難度萝快。
(3) 系統(tǒng)擴(kuò)展困難锻霎,一旦添加新產(chǎn)品就不得不修改工廠邏輯,在產(chǎn)品類型較多時(shí)揪漩,有可能造成工廠邏輯過于復(fù)雜量窘,不利于系統(tǒng)的擴(kuò)展和維護(hù)。
(4) 簡單工廠模式由于使用了靜態(tài)工廠方法氢拥,造成工廠角色無法形成基于繼承的等級(jí)結(jié)構(gòu)蚌铜。
- 適用場景
在以下情況下可以考慮使用簡單工廠模式:
(1) 工廠類負(fù)責(zé)創(chuàng)建的對(duì)象比較少,由于創(chuàng)建的對(duì)象較少嫩海,不會(huì)造成工廠方法中的業(yè)務(wù)邏輯太過復(fù)雜冬殃。
(2) 客戶端只知道傳入工廠類的參數(shù),對(duì)于如何創(chuàng)建對(duì)象并不關(guān)心叁怪。
練習(xí)
使用簡單工廠模式設(shè)計(jì)一個(gè)可以創(chuàng)建不同幾何形狀(如圓形审葬、方形和三角形等)的繪圖工具,每個(gè)幾何圖形都具有繪制draw()和擦除erase()兩個(gè)方法,要求在繪制不支持的幾何圖形時(shí)涣觉,提示一個(gè)UnSupportedShapeException痴荐。
練習(xí)會(huì)在我的github上做掉