創(chuàng)建型設(shè)計(jì)模式:
簡(jiǎn)單工廠模式、工廠方法模式馅扣、抽象工廠模式斟赚、單例模式、原型模式和建造者模式差油。 統(tǒng)共六種拗军。
本此分享三種工廠模式中的簡(jiǎn)單工廠模式。
記錄結(jié)構(gòu):
–1.實(shí)際問(wèn)題引入(需求)
--2.傳統(tǒng)解決辦法
--3.傳統(tǒng)解決辦法帶來(lái)的問(wèn)題
--4.使用xx模式解決問(wèn)題
--4.1xx模式簡(jiǎn)述
--4.2xx模式類圖表示
--4.3xx模式完整解決方案
--5.方案改進(jìn)
--6.模式總結(jié)
1.簡(jiǎn)單工廠模式:
1.1實(shí)際問(wèn)題引入
YY軟件公司欲基于 Java 語(yǔ)言開(kāi)發(fā)一套圖表庫(kù),該圖表庫(kù)可以為應(yīng)用系統(tǒng)提供各種不同外觀的圖表,例如柱 狀圖、餅狀圖发侵、折線圖等交掏。Sunny 軟件公司圖表庫(kù)設(shè)計(jì)人員希望為應(yīng)用系統(tǒng)開(kāi)發(fā)人員提供一套靈活易用的圖表 庫(kù),而且可以較為方便地對(duì)圖表庫(kù)進(jìn)行擴(kuò)展,以便能夠在將來(lái)增加一些新類型的圖表。
2.2傳統(tǒng)解決辦法:
Sunny 軟件公司圖表庫(kù)設(shè)計(jì)人員提出了一個(gè)初始設(shè)計(jì)方案,將所有圖表的實(shí)現(xiàn)代碼封裝在一個(gè) Chart 類中,其框 架代碼如下所示:
/**
* Created by tao.liu on 16/10/18.
*/
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")) {
//顯示折線圖
}
}
}
客戶端代碼通過(guò)調(diào)用 Chart 類的構(gòu)造函數(shù)來(lái)創(chuàng)建圖表對(duì)象,根據(jù)參數(shù) type 的不同可以得到不同類型的圖表,然 后再調(diào)用 display() 方法來(lái)顯示相應(yīng)的圖表器紧。
3.傳統(tǒng)解決辦法帶來(lái)的問(wèn)題
不難看出,Chart 類是一個(gè)“巨大的”類,在該類的設(shè)計(jì)中存在如下幾個(gè)問(wèn)題:
(1) 在 Chart 類中包含很多if...else...代碼塊,整個(gè)類的代碼相當(dāng)冗長(zhǎng),代碼越長(zhǎng),閱讀難度耀销、維護(hù)難度和測(cè)試 難度也越大;而且大量條件語(yǔ)句的存在還將影響系統(tǒng)的性能,程序在執(zhí)行過(guò)程中需要做大量的條件判斷。
(2) Chart 類的職責(zé)過(guò)重,它負(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 類的源代碼,違反了“開(kāi)閉原則”熊尉。
(4) 客戶端只能通過(guò) new 關(guān)鍵字來(lái)直接創(chuàng)建 Chart 對(duì)象,Chart 類與客戶端類耦合度較高,對(duì)象的創(chuàng)建和使用無(wú) 法分離。
(5) 客戶端在創(chuàng)建 Chart 對(duì)象之前可能還需要進(jìn)行大量初始化設(shè)置,例如設(shè)置柱狀圖的顏色掌腰、高度等,如果在 Chart 類的構(gòu)造函數(shù)中沒(méi)有提供一個(gè)默認(rèn)設(shè)置,那就只能由客戶端來(lái)完成初始設(shè)置,這些代碼在每次創(chuàng)建 Chart 對(duì) 象時(shí)都會(huì)出現(xiàn),導(dǎo)致代碼的重復(fù)狰住。
面對(duì)一個(gè)如此巨大、職責(zé)如此重,且與客戶端代碼耦合度非常高的類,我們應(yīng)該怎么辦?本章將要介紹的簡(jiǎn)單工廠模式將在一定程度上解決上述問(wèn)題齿梁。
4.使用簡(jiǎn)單工廠模式解決問(wèn)題
4.1簡(jiǎn)單工廠模式簡(jiǎn)述:
簡(jiǎn)單工廠模式并不屬于 GoF 23 個(gè)經(jīng)典設(shè)計(jì)模式,但通常將它作為學(xué)習(xí)其他工廠模式的基礎(chǔ),它的設(shè)計(jì)思想很簡(jiǎn)單,其基本流程如下:
首先將需要?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ì)象催植。
4.2簡(jiǎn)單工廠模式類圖表示
在簡(jiǎn)單工廠模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
Factory(工廠角色):工廠角色即工廠類,它是簡(jiǎn)單工廠模式的核心,負(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)品角色):它是簡(jiǎn)單工廠模式的創(chuàng)建目標(biāo),所有被創(chuàng)建的對(duì)象都充當(dāng)這個(gè)角色的 某個(gè)具體類的實(shí)例创南。每一個(gè)具體產(chǎn)品角色都繼承了抽象產(chǎn)品角色,需要實(shí)現(xiàn)在抽象產(chǎn)品中聲明的抽象方法。
在簡(jiǎn)單工廠模式中,客戶端通過(guò)工廠類來(lái)創(chuàng)建一個(gè)產(chǎn)品類的實(shí)例,而無(wú)須直接使用 new 關(guān)鍵字來(lái)創(chuàng)建對(duì)象,它是工廠模式家族中最簡(jiǎn)單的一員省核。
分部代碼展示:
1.在使用簡(jiǎn)單工廠模式時(shí),首先需要對(duì)產(chǎn)品類進(jìn)行重構(gòu),不能設(shè)計(jì)一個(gè)包羅萬(wàn)象的產(chǎn)品類,而需根據(jù)實(shí)際情況設(shè)計(jì) 一個(gè)產(chǎn)品層次結(jié)構(gòu),將所有產(chǎn)品類公共的代碼移至抽象產(chǎn)品類,并在抽象產(chǎn)品類中聲明一些抽象方法,以供不同 的具體產(chǎn)品類來(lái)實(shí)現(xiàn),典型的抽象產(chǎn)品類代碼如下所示:
/**
* Created by tao.liu on 16/10/18.
*/
abstract class Product {
//所有產(chǎn)品類的公共業(yè)務(wù)方法
public void methodSame() {
//公共方法的實(shí)現(xiàn)
}
//聲明抽象業(yè)務(wù)方法
public abstract void methodDiff();
}
2.在具體產(chǎn)品類中實(shí)現(xiàn)了抽象產(chǎn)品類中聲明的抽象業(yè)務(wù)方法,不同的具體產(chǎn)品類可以提供不同的實(shí)現(xiàn),典型的具體 產(chǎn)品類代碼如下所示:
/**
* Created by tao.liu on 16/10/18.
*/
class ConcreteProduct extends Product {
//實(shí)現(xiàn)業(yè)務(wù)方法
public void methodDiff() {
//業(yè)務(wù)方法的實(shí)現(xiàn)
}
}
3.簡(jiǎn)單工廠模式的核心是工廠類,在沒(méi)有工廠類之前,客戶端一般會(huì)使用 new 關(guān)鍵字來(lái)直接創(chuàng)建產(chǎn)品對(duì)象,而在引 入工廠類之后,客戶端可以通過(guò)工廠類來(lái)創(chuàng)建產(chǎn)品,在簡(jiǎn)單工廠模式中,工廠類提供了一個(gè)靜態(tài)工廠方法供客戶 端使用,根據(jù)所傳入的參數(shù)不同可以創(chuàng)建不同的產(chǎn)品對(duì)象,典型的工廠類代碼如下所示:
/**
* Created by tao.liu on 16/10/18.
*/
class Factory {
//靜態(tài)工廠方法
public static Product getProduct(String arg) {
Product product = null;
if (arg.equalsIgnoreCase("A")) {
//初始化設(shè)置productA
product = new ConcreteProductA();
} else if (arg.equalsIgnoreCase("B")) {
//初始化設(shè)置productB
product = new ConcreteProductB();
}
return product;
}
}
4.在客戶端代碼中,我們通過(guò)調(diào)用工廠類的工廠方法即可得到產(chǎn)品對(duì)象,典型代碼如下所示:
/**
* Created by tao.liu on 16/10/18.
*/
class Client {
public static void main(String args[]) {
Product product;
//通過(guò)工廠類創(chuàng)建產(chǎn)品對(duì)象稿辙,沒(méi)有通過(guò)new關(guān)鍵字
product = Factory.getProduct("A");
product.methodSame();
product.methodDiff();
}
}
4.3簡(jiǎn)單工廠模式完整解決方案
完整解決方案:
為了將 Chart 類的職責(zé)分離,同時(shí)將 Chart 對(duì)象的創(chuàng)建和使用分離,Sunny 軟件公司開(kāi)發(fā)人員決定使用簡(jiǎn)單工 廠模式對(duì)圖表庫(kù)進(jìn)行重構(gòu),重構(gòu)后的結(jié)構(gòu)如圖所示:
圖片 1.2 圖表庫(kù)結(jié)構(gòu)圖
在圖中,Chart 接口充當(dāng)抽象產(chǎn)品類,其子類HistogramChart、PieChart 和 LineChart 充當(dāng)具體產(chǎn)品類,Ch artFactory 充當(dāng)工廠類气忠。完整代碼如下所示:
/**
* Created by tao.liu on 16/10/18.
*/
//抽象圖表接口:抽象產(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;
}
}
編寫如下客戶端測(cè)試代碼:
/**
* Created by tao.liu on 16/10/18.
*/
class Client {
public static void main(String args[]) {
Chart chart;
chart = ChartFactory.getChart("histogram"); //通過(guò)靜態(tài)工廠方法創(chuàng)建產(chǎn)品
chart.display();
}
}
編譯并運(yùn)行程序,輸出結(jié)果如下:
創(chuàng)建柱狀圖袱瓮!
初始化設(shè)置柱狀圖!
顯示柱狀圖爱咬!
在客戶端測(cè)試類中,我們使用工廠類的靜態(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è)置餅狀圖精拟!
顯示餅狀圖燎斩!
5.方案改進(jìn)
YY軟件公司開(kāi)發(fā)人員發(fā)現(xiàn)在創(chuàng)建具體 Chart 對(duì)象時(shí),每更換一個(gè) Chart 對(duì)象都需要修改客戶端代碼中靜態(tài) 工廠方法的參數(shù),客戶端代碼將要重新編譯,這對(duì)于客戶端而言,違反了“開(kāi)閉原則”,有沒(méi)有一種方法能夠在 不修改客戶端代碼的前提下更換具體產(chǎn)品對(duì)象呢?答案是肯定的,下面將介紹一種常用的實(shí)現(xiàn)方式虱歪。
我們可以將靜態(tài)工廠方法的參數(shù)存儲(chǔ)在 XML 或 properties 格式的配置文件中,如下 config.xml 所示:
histogram
再通過(guò)一個(gè)工具類 XMLUtil 來(lái)讀取配置文件中的字符串參數(shù),XMLUtil 類的代碼如下所示:
/**
* Created by tao.liu on 16/10/18.
*/
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;
}
}
}
修改之后客戶端調(diào)用代碼如下所示:
class Client {
public static void main(String args[]) {
Chart chart;
String type = XMLUtil.getChartType(); //讀取配置文件中的參數(shù)
chart = ChartFactory.getChart(type); //創(chuàng)建產(chǎn)品對(duì)象
chart.display();
}
}
不難發(fā)現(xiàn)栅表,在上述客戶端代碼中不包含任何與具體圖表對(duì)象相關(guān)的信息笋鄙,如果需要更換具體圖表對(duì)象,只需修改配置文件 config.xml怪瓶,無(wú)須修改任何源代碼萧落,符合“開(kāi)閉原則”。
6.模式總結(jié):
簡(jiǎn)單工廠模式提供了專門的工廠類用于創(chuàng)建對(duì)象洗贰,將對(duì)象的創(chuàng)建和對(duì)象的使用分離開(kāi)找岖,它作為一種最簡(jiǎn)單的工廠模式在軟件開(kāi)發(fā)中得到了較為廣泛的應(yīng)用。
簡(jiǎn)單工廠模式的主要優(yōu)點(diǎn)如下:
(1) 工廠類包含必要的判斷邏輯敛滋,可以決定在什么時(shí)候創(chuàng)建哪一個(gè)產(chǎn)品類的實(shí)例许布,客戶端可以免除直接創(chuàng)建產(chǎn)品對(duì)象的職責(zé),而僅僅“消費(fèi)”產(chǎn)品绎晃,簡(jiǎn)單工廠模式實(shí)現(xiàn)了對(duì)象創(chuàng)建和使用的分離蜜唾。
(2) 客戶端無(wú)須知道所創(chuàng)建的具體產(chǎn)品類的類名,只需要知道具體產(chǎn)品類所對(duì)應(yīng)的參數(shù)即可庶艾,對(duì)于一些復(fù)雜的類名袁余,通過(guò)簡(jiǎn)單工廠模式可以在一定程度減少使用者的記憶量。
(3) 通過(guò)引入配置文件咱揍,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產(chǎn)品類泌霍,在一定程度上提高了系統(tǒng)的靈活性。
簡(jiǎn)單工廠模式的主要缺點(diǎn)如下:
(1) 由于工廠類集中了所有產(chǎn)品的創(chuàng)建邏輯述召,職責(zé)過(guò)重,一旦不能正常工作蟹地,整個(gè)系統(tǒng)都要受到影響积暖。
(2) 使用簡(jiǎn)單工廠模式勢(shì)必會(huì)增加系統(tǒng)中類的個(gè)數(shù)(引入了新的工廠類),增加了系統(tǒng)的復(fù)雜度和理解難度怪与。
(3) 系統(tǒng)擴(kuò)展困難夺刑,一旦添加新產(chǎn)品就不得不修改工廠邏輯,在產(chǎn)品類型較多時(shí)分别,有可能造成工廠邏輯過(guò)于復(fù)雜遍愿,不利于系統(tǒng)的擴(kuò)展和維護(hù)。
(4) 簡(jiǎn)單工廠模式由于使用了靜態(tài)工廠方法耘斩,造成工廠角色無(wú)法形成基于繼承的等級(jí)結(jié)構(gòu)沼填。
適用場(chǎng)景
在以下情況下可以考慮使用簡(jiǎn)單工廠模式:
(1) 工廠類負(fù)責(zé)創(chuàng)建的對(duì)象比較少,由于創(chuàng)建的對(duì)象較少括授,不會(huì)造成工廠方法中的業(yè)務(wù)邏輯太過(guò)復(fù)雜坞笙。
(2) 客戶端只知道傳入工廠類的參數(shù)岩饼,對(duì)于如何創(chuàng)建對(duì)象并不關(guān)心。