一、需求
比如我想開發一套圖表庫,該圖表庫可以爲應用系統提供各種不同外觀的圖表,例如柱狀圖、餅狀圖、折線圖等,那我們可以這樣寫。代碼如下:
public class Chart {
private String type; // 圖表類型
public Chart(String type) {
this.type = type;
if (type.equalsIgnoreCase("histogram")) {
// 初始化柱狀圖
System.out.println("初始化柱狀圖 ");
} else if (type.equalsIgnoreCase("pie")) {
// 初始化餅狀圖
System.out.println("初始化餅狀圖 ");
} else if (type.equalsIgnoreCase("line")) {
// 初始化折線圖
System.out.println("初始化折線圖 ");
}
}
public void display() {
if (this.type.equalsIgnoreCase("histogram")) {
// 顯示柱狀圖
System.out.println("顯示柱狀圖 ");
} else if (this.type.equalsIgnoreCase("pie")) {
// 顯示餅狀圖
System.out.println("顯示餅狀圖 ");
} else if (this.type.equalsIgnoreCase("line")) {
// 顯示折線圖
System.out.println("顯示折線圖 ");
}
}
}
public class Client {
public static void main(String[] args) {
Chart chart = new Chart("histogram");
chart.display();
}
}
初始化柱狀圖
顯示柱狀圖
客戶端代碼通過調用Chart類的構造函數來創建圖表對象,根據參數type的不同可以得到不同類型的圖表,然後再調用display()方法來顯示相應的圖表。
不難看出,Chart類是一個“巨大的”類,在該類的設計中存在如下幾個問題:
- 在Chart類中包含很多“if…else…”代碼塊,整個類的代碼相當冗長,代碼越長,閱讀難度、維護難度和測試難度也越大;而且大量條件語句的存在還將影響系統的性能,程序在執行過程中需要做大量的條件判斷。
- Chart類的職責過重,它負責初始化和顯示所有的圖表對象,將各種圖表對象的初始化代碼和顯示代碼集中在一個類中實現,違反了“單一職責原則”,不利於類的重用和維護;而且將大量的對象初始化代碼都寫在構造函數中將導致構造函數非常龐大,對象在創建時需要進行條件判斷,降低了對象創建的效率。
- 當需要增加新類型的圖表時,必須修改Chart類的源代碼,違反了“開閉原則”。
- 客戶端只能通過new關鍵字來直接創建Chart對象,Chart類與客戶端類耦合度較高,對象的創建和使用無法分離。
- 客戶端在創建Chart對象之前可能還需要進行大量初始化設置,例如設置柱狀圖的顏色、高度等,如果在Chart類的構造函數中沒有提供一個默認設置,那就只能由客戶端來完成初始設置,這些代碼在每次創建Chart對象時都會出現,導致代碼的重複。
面對一個如此巨大、職責如此重,且與客戶端代碼耦合度非常高的類,我們應該怎麼辦?簡單工廠模式將在一定程度上解決上述問題。
二、簡單工廠模式概述
簡單工廠模式並不屬於GoF 23個經典設計模式,但通常將它作爲學習其他工廠模式的基礎,它的設計思想很簡單
簡單工廠模式流程
簡單工廠其基本流程如下:首先將需要創建的各種不同對象(例如各種不同的Chart對象)的相關代碼封裝到不同的類中,這些類稱爲具體產品類,而將它們公共的代碼進行抽象和提取後封裝在一個抽象產品類中,每一個具體產品類都是抽象產品類的子類;然後提供一個工廠類用於創建各種產品,在工廠類中提供一個創建產品的工廠方法,該方法可以根據所傳入的參數不同創建不同的具體產品對象;客戶端只需調用工廠類的工廠方法並傳入相應的參數即可得到一個產品對象。
簡單工廠模式定義
簡單工廠模式(Simple Factory Pattern):定義一個工廠類,它可以根據參數的不同返回不同類的實例,被創建的實例通常都具有共同的父類。因爲在簡單工廠模式中用於創建實例的方法是靜態(static)方法,因此簡單工廠模式又被稱爲靜態工廠方法(Static Factory Method)模式,它屬於類創建型模式。
簡單工廠模式的要點在於:當你需要什麼,只需要傳入一個正確的參數,就可以獲取你所需要的對象,而無須知道其創建細節。簡單工廠模式結構比較簡單,其核心是工廠類的設計,其結構如圖所示:
簡單工廠模式中的角色
- Factory(工廠角色):工廠角色即工廠類,它是簡單工廠模式的核心,負責實現創建所有產品實例的內部邏輯;工廠類可以被外界直接調用,創建所需的產品對象;在工廠類中提供了靜態的工廠方法factoryMethod(),它的返回類型爲抽象產品類型Product。
- Product(抽象產品角色):它是工廠類所創建的所有對象的父類,封裝了各種產品對象的公有方法,它的引入將提高系統的靈活性,使得在工廠類中只需定義一個通用的工廠方法,因爲所有創建的具體產品對象都是其子類對象。
- ConcreteProduct(具體產品角色):它是簡單工廠模式的創建目標,所有被創建的對象都充當這個角色的某個具體類的實例。每一個具體產品角色都繼承了抽象產品角色,需要實現在抽象產品中聲明的抽象方法。
簡單工廠模式代碼示例
在簡單工廠模式中,客戶端通過工廠類來創建一個產品類的實例,而無須直接使用new關鍵字來創建對象,它是工廠模式家族中最簡單的一員。
在使用簡單工廠模式時,首先需要對產品類進行重構,不能設計一個包羅萬象的產品類,而需根據實際情況設計一個產品層次結構,將所有產品類公共的代碼移至抽象產品類,並在抽象產品類中聲明一些抽象方法,以供不同的具體產品類來實現,典型的抽象產品類代碼如下所示:
public abstract class Product {
//所有產品類的公共業務方法
public void methodSame() {
//公共方法的實現
System.out.println("採購原材料");
}
//聲明抽象業務方法
public abstract void methodDiff();
}
在具體產品類中實現了抽象產品類中聲明的抽象業務方法,不同的具體產品類可以提供不同的實現,典型的具體產品類代碼如下所示:
public class ProductA extends Product{
@Override
public void methodDiff() {
System.out.println("生成A商品");
}
}
public class ProductB extends Product{
@Override
public void methodDiff() {
System.out.println("生成B商品");
}
}
簡單工廠模式的核心是工廠類,在沒有工廠類之前,客戶端一般會使用new關鍵字來直接創建產品對象,而在引入工廠類之後,客戶端可以通過工廠類來創建產品,在簡單工廠模式中,工廠類提供了一個靜態工廠方法供客戶端使用,根據所傳入的參數不同可以創建不同的產品對象,典型的工廠類代碼如下所示:
public class Factory {
//靜態工廠方法
public static Product getProduct(String arg) {
Product product = null;
if (arg.equalsIgnoreCase("A")) {
product = new ProductA();
//初始化設置product
}
else if (arg.equalsIgnoreCase("B")) {
product = new ProductB();
//初始化設置product
}
return product;
}
}
在客戶端代碼中,我們通過調用工廠類的工廠方法即可得到產品對象,典型代碼如下所示:
public class FactoryClient {
public static void main(String args[]) {
Product product;
product = Factory.getProduct("A"); //通過工廠類創建產品對象
product.methodSame();
product.methodDiff();
product = Factory.getProduct("B"); //通過工廠類創建產品對象
product.methodSame();
product.methodDiff();
}
}
三、需求優化
我們對簡單工廠模式已經有了初步的瞭解,那我們根據上面的角色定義與代碼把圖表庫代碼改一下,重構後的類圖如下:
Chart接口充當抽象產品類,其子類HistogramChart、PieChart和LineChart充當具體產品類,ChartFactory充當工廠類。完整代碼如下所示:
//抽象圖表接口:抽象產品類
interface Chart {
public void display();
}
//柱狀圖類:具體產品類
class HistogramChart implements Chart {
public HistogramChart() {
System.out.println("創建柱狀圖!");
}
public void display() {
System.out.println("顯示柱狀圖!");
}
}
//餅狀圖類:具體產品類
class PieChart implements Chart {
public PieChart() {
System.out.println("創建餅狀圖!");
}
public void display() {
System.out.println("顯示餅狀圖!");
}
}
//折線圖類:具體產品類
class LineChart implements Chart {
public LineChart() {
System.out.println("創建折線圖!");
}
public void display() {
System.out.println("顯示折線圖!");
}
}
//圖表工廠類:工廠類
class ChartFactory {
//靜態工廠方法
public static Chart getChart(String type) {
Chart chart = null;
if (type.equalsIgnoreCase("histogram")) {
chart = new HistogramChart();
System.out.println("初始化設置柱狀圖!");
}
else if (type.equalsIgnoreCase("pie")) {
chart = new PieChart();
System.out.println("初始化設置餅狀圖!");
}
else if (type.equalsIgnoreCase("line")) {
chart = new LineChart();
System.out.println("初始化設置折線圖!");
}
return chart;
}
}
class Client {
public static void main(String args[]) {
Chart chart;
chart = ChartFactory.getChart("histogram"); //通過靜態工廠方法創建產品
chart.display();
}
}
在客戶端測試類中,我們使用工廠類的靜態工廠方法創建產品對象,如果需要更換產品,只需修改靜態工廠方法中的參數即可,例如將柱狀圖改爲餅狀圖,只需將代碼:
chart = ChartFactory.getChart("histogram"); //histogram 換成 pie 即可
四、 簡單工廠模式總結
簡單工廠模式提供了專門的工廠類用於創建對象,將對象的創建和對象的使用分離開,它作爲一種最簡單的工廠模式在軟件開發中得到了較爲廣泛的應用。
主要優點
- 工廠類包含必要的判斷邏輯,可以決定在什麼時候創建哪一個產品類的實例,客戶端可以免除直接創建產品對象的職責,而僅僅“消費”產品,簡單工廠模式實現了對象創建和使用的分離。
- 客戶端無須知道所創建的具體產品類的類名,只需要知道具體產品類所對應的參數即可,對於一些複雜的類名,通過簡單工廠模式可以在一定程度減少使用者的記憶量。
- 通過引入配置文件,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產品類,在一定程度上提高了系統的靈活性。
主要缺點
- 由於工廠類集中了所有產品的創建邏輯,職責過重,一旦不能正常工作,整個系統都要受到影響。
- 使用簡單工廠模式勢必會增加系統中類的個數(引入了新的工廠類),增加了系統的複雜度和理解難度。
- 系統擴展困難,一旦添加新產品就不得不修改工廠邏輯,在產品類型較多時,有可能造成工廠邏輯過於複雜,不利於系統的擴展和維護。
- 簡單工廠模式由於使用了靜態工廠方法,造成工廠角色無法形成基於繼承的等級結構。 適用場景
使用場景
-工廠類負責創建的對象比較少,由於創建的對象較少,不會造成工廠方法中的業務邏輯太過複雜。
- 客戶端只知道傳入工廠類的參數,對於如何創建對象並不關心。