工廠模式概述
意義
- 問題引出
在面向對象編程中, 最通常的方法是一個new操作符產生一個對象實例,new操作符就是用來構造對象實例的赢织。當我們使用 new 來構造一個新的類實例時画机,其實是告訴了 JVM 我需要一個新的實例敏沉。JVM 就會自動在內存中開辟一片空間,然后調用構造函數來初始化成員變量凫佛,最終把引用返回給調用方溉躲。
但是在一些情況下, new操作符直接生成對象會帶來一些問題胚宦。舉例來說, 許多類型對象的創(chuàng)造需要一系列的步驟: 你可能需要計算或取得對象的初始設置; 選擇生成哪個子對象實例; 或在生成你需要的對象之前必須先生成一些輔助功能的對象。在這些情況,新對象的建立就是一個 “過程”戒洼,不僅是一個操作俏橘,像一部大機器中的一個齒輪傳動。 - 解決的問題
如何能輕松方便地構造對象實例施逾,而不必關心構造對象實例的細節(jié)和復雜過程呢敷矫? - 一個核心思想
在所有的工廠模式中,兩個類A和B之間的關系應該僅僅是A創(chuàng)建B或是A使用B汉额,而不能兩種關系都有曹仗。
將對象的創(chuàng)建和使用分離,也使得系統更加符合“單一職責原則”蠕搜,有利于對功能的復用和系統的維護怎茫。
概念
GOF為工廠模式的定義:
“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”
在基類中定義創(chuàng)建對象的一個接口,讓子類決定實例化哪個類妓灌。工廠方法讓一個類的實例化延遲到子類中進行轨蛤。
- 用工廠方法代替new操作來實例化對象
- 定義一個接口來創(chuàng)建對象,但是讓子類來決定哪些類需要被實例化
適用場合
- 有一組類似的對象需要創(chuàng)建虫埂。
- 在編碼時不能預見需要創(chuàng)建哪些種類的實例祥山。
- 系統需要考慮擴展性,不應依賴于產品類實例如何被創(chuàng)建掉伏、組合和表達的細節(jié)缝呕。
分類
- 簡單工廠(Simple Factory)模式澳窑,又稱靜態(tài)工廠方法模式(Static Factory Method Pattern)。
- 工廠方法(Factory Method)模式供常,又稱多態(tài)性工廠(Polymorphic Factory)模式或虛擬構造子(Virtual Constructor)模式摊聋;
- 抽象工廠(Abstract Factory)模式,又稱工具箱(Kit 或Toolkit)模式栈暇。
聯系
這三種模式從上到下逐步抽象麻裁,并且更具一般性。
GOF在《設計模式》一書中將工廠模式分為兩類:工廠方法模式(Factory Method)與抽象工廠模式(Abstract Factory)源祈。
將簡單工廠模式(Simple Factory)看為工廠方法模式的一種特例煎源,兩者歸為一類。
區(qū)別
工廠方法模式:
一個抽象產品類新博,可以派生出多個具體產品類薪夕。
一個抽象工廠類,可以派生出多個具體工廠類赫悄。
每個具體工廠類只能創(chuàng)建一個具體產品類的實例。
抽象工廠模式:
多個抽象產品類馏慨,每個抽象產品類可以派生出多個具體產品類埂淮。
一個抽象工廠類,可以派生出多個具體工廠類写隶。
每個具體工廠類可以創(chuàng)建多個具體產品類的實例倔撞。
簡單工廠模式(靜態(tài)工廠方法)
定義
定義一個工廠類,可以根據參數的不同返回不同類的實例慕趴。簡單工廠模式專門定義一個類來負責創(chuàng)建其他類的實例痪蝇,被創(chuàng)建的實例通常都具有共同的父類。因為在簡單工廠模式中用于創(chuàng)建實例的方法是靜態(tài)(static)方法冕房,因此簡單工廠模式又被稱為靜態(tài)工廠方法(Static Factory Method)模式躏啰,它屬于類創(chuàng)建型模式。
地位
簡單工廠模式并不是23種常用的設計模式之一耙册,它只算工廠模式的一個特殊實現给僵。簡單工廠模式在實際中的應用相對于其他2個工廠模式用的還是相對少得多,因為它只適應很多簡單的情況详拙。違背了我們在概述中說的 開放-封閉原則 帝际,每次要新添加一個功能,都需要在生switch-case 語句(或者if-else 語句)中去修改代碼饶辙,添加分支條件蹲诀。
考慮使用靜態(tài)工廠方法代替構造器
Effective Java中考慮使用靜態(tài)工廠方法代替構造器,其原因作者總結了 4 條(第二版):
1 靜態(tài)工廠方法與構造器不同的第一優(yōu)勢在于弃揽,它們有名字
由于語言的特性脯爪,Java 的構造函數都是跟類名一樣的珊佣。這導致的一個問題是構造函數的名稱不夠靈活,經常不能準確地描述返回值披粟,在有多個重載的構造函數時尤甚咒锻,如果參數類型、數目又比較相似的話守屉,那更是很容易出錯惑艇。
比如,如下的一段代碼 :
Date date0 = new Date();
Date date1 = new Date(0L);
Date date2 = new Date("0");
Date date3 = new Date(1,2,1);
Date date4 = new Date(1,2,1,1,1);
Date date5 = new Date(1,2,1,1,1,1);
—— Date 類有很多重載函數拇泛,對于開發(fā)者來說滨巴,假如不是特別熟悉的話,恐怕是需要猶豫一下俺叭,才能找到合適的構造函數的恭取。而對于其他的代碼閱讀者來說,估計更是需要查看文檔熄守,才能明白每個參數的含義了蜈垮。
(當然,Date 類在目前的 Java 版本中裕照,只保留了一個無參和一個有參的構造函數攒发,其他的都已經標記為 @Deprecated 了)
而如果使用靜態(tài)工廠方法,就可以給方法起更多有意義的名字晋南,比如前面的 valueOf
惠猿、newInstance
、getInstance
等负间,對于代碼的編寫和閱讀都能夠更清晰偶妖。
2.2 第二個優(yōu)勢,不用每次被調用時都創(chuàng)建新對象
這個很容易理解了政溃,有時候外部調用者只需要拿到一個實例趾访,而不關心是否是新的實例;又或者我們想對外提供一個單例時 —— 如果使用工廠方法玩祟,就可以很容易的在內部控制腹缩,防止創(chuàng)建不必要的對象,減少開銷空扎。
在實際的場景中藏鹊,單例的寫法也大都是用靜態(tài)工廠方法來實現的。
2.3 第三個優(yōu)勢转锈,可以返回原返回類型的子類
這條不用多說盘寡,設計模式中的基本的原則之一——『里氏替換』原則,就是說子類應該能替換父類撮慨。
顯然竿痰,構造方法只能返回確切的自身類型脆粥,而靜態(tài)工廠方法則能夠更加靈活,可以根據需要方便地返回任何它的子類型的實例
Class Person {
public static Person getInstance(){
return new Person();
// 這里可以改為 return new Player() / Cooker()
}
}
Class Player extends Person{
}
Class Cooker extends Person{
}
比如上面這段代碼影涉,Person 類的靜態(tài)工廠方法可以返回 Person 的實例变隔,也可以根據需要返回它的子類 Player 或者 Cooker。(當然蟹倾,這只是為了演示匣缘,在實際的項目中,一個類是不應該依賴于它的子類的鲜棠。但如果這里的 getInstance () 方法位于其他的類中肌厨,就更具有的實際操作意義了)
2.4 第四個優(yōu)勢,在創(chuàng)建帶泛型的實例時豁陆,能使代碼變得簡潔
這條主要是針對帶泛型類的繁瑣聲明而說的柑爸,需要重復書寫兩次泛型參數:
Map<String,Date> map = new HashMap<String,Date>();
不過自從 java7 開始,這種方式已經被優(yōu)化過了 —— 對于一個已知類型的變量進行賦值時盒音,由于泛型參數是可以被推導出表鳍,所以可以在創(chuàng)建實例時省略掉泛型參數。
Map<String,Date> map = new HashMap<>();
所以這個問題實際上已經不存在了里逆。
角色
工廠類角色---Factory
- 工廠角色即工廠類进胯,它是簡單工廠模式的核心,負責實現創(chuàng)建所有產品實例的內部邏輯
- 工廠類可以被外界直接調用原押,創(chuàng)建所需的產品對象
抽象產品角色---Product
它是工廠類所創(chuàng)建的所有對象的父類,所創(chuàng)建的具體產品對象都是其子類對象
具體產品角色---Concrete Product
工廠方法模式所創(chuàng)建的任何對象都是這個角色的實例
適用場合
- 需要創(chuàng)建的對象較少偎血,對象少不會造成工廠方法中的業(yè)務邏輯太過復雜
- 客戶端只知道傳入工廠類的參數诸衔,對于如何創(chuàng)建對象并不關心
使用方式
- 在工廠類中提供一個創(chuàng)建產品的工廠方法,該方法可以根據所傳入的參數不同創(chuàng)建不同的具體產品對象
- 客戶端只需調用工廠類的工廠方法并傳入相應的參數即可得到一個產品對象颇玷,而無須直接使用new關鍵字來創(chuàng)建對象笨农。
一個例子
- 產品接口類:Product.java
public interface Product {
}
- 具體的產品類:Iphone.java
public class Iphone implements Product {
public Iphone() {
System.out.println("Iphone被制造了");
}
}
- 具體的產品類:Ipad.java
public class Ipad implements Product {
public Ipad() {
System.out.println("Ipad被制造了");
}
}
- 具體的產品類:Iwatch.java
public class Iwatch implements Product {
public Iwatch() {
System.out.println("Iwatch被制造了");
}
}
- 生產產品的工廠類 (簡單的工廠類/靜態(tài)工廠類):ProductFactory.java
public class ProductFactory {
public static Product produce(String productName) {
if(productName == null){
return null;
}
switch (productName) {
case "Iphone":
return new Iphone();
case "Ipad":
return new Ipad();
case "Iwatch":
return new Iwatch();
default:
System.out.println("沒有該類產品");
return null;
}
}
}
- 客戶端調用工廠類靜態(tài)方法創(chuàng)建產品:Client.java
public class Client {
public static void main(String[] args) {
ProductFactory.produce("Iphone");
ProductFactory.produce("Ipad");
ProductFactory.produce("Iwatch");
ProductFactory.produce("Ipod");
}
}
這樣的實現有個問題,如果我們新增產品類的話帖渠,就需要修改工廠類中的produce()
方法谒亦,這很明顯不符合 開放-封閉原則 。
簡單工廠模式的缺點如下:
(1)由于工廠類集中了所有產品創(chuàng)建邏輯空郊,一旦不能正常工作份招,整個系統都要受到影響。
(2)使用簡單工廠模式將會增加系統中類的個數狞甚,在一定程序上增加了系統的復雜度和理解難度锁摔。
(3)系統擴展困難,一旦添加新產品就不得不修改工廠邏輯哼审,在產品類型較多時谐腰,有可能造成工廠邏輯過于復雜孕豹,不利于系統的擴展和維護。
(4)簡單工廠模式由于使用了靜態(tài)工廠方法十气,造成工廠角色無法形成基于繼承的等級結構励背。
工廠方法模式
定義
工廠方法模式去掉了簡單工廠模式中工廠方法的靜態(tài)屬性,使得它可以被子類繼承砸西。這樣在簡單工廠模式里集中在工廠方法上的壓力可以由工廠方法模式里不同的工廠子類來分擔叶眉。這樣使得結構變得靈活起來——當有新的產品產生時
,只要按照抽象產品角色籍胯、抽象工廠角色提供的合同來生成竟闪,那么就可以被客戶使用,而不必去修改任何已有的代碼杖狼。可以看出工廠角色的結構也是符合開閉原則的
工廠方法模式的優(yōu)缺點
優(yōu)點
(1)在工廠方法模式中炼蛤,工廠方法用來創(chuàng)建客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被實例化這一細節(jié)蝶涩,用戶只需要關心所需產品對應的工廠理朋,無需關心創(chuàng)建細節(jié),甚至無需知道具體產品類的類名绿聘。
(2)基于工廠角色和產品角色的多態(tài)性設計是工廠方法模式的關鍵嗽上。它能夠使工廠可以自主確定創(chuàng)建何種產品對象,而如何創(chuàng)建這個對象的細節(jié)則完全封裝在具體工廠內部熄攘。工廠方法模式之所以又被稱為多態(tài)工廠模式兽愤,正是因為所有的具體工廠類都具有同一抽象父類。
(3)使用工廠方法模式的另一個優(yōu)點是在系統中加入新產品時挪圾,無需修改抽象工廠和抽象產品提供的接口浅萧,無需修改客戶端,也無需修改其他的具體工廠和具體產品哲思,而只要添加一個具體工廠和具體產品就可以了洼畅,這樣,系統的可擴展性也就變得非常好棚赔,完全符合“開閉原則”帝簇。
缺點
(1)在添加新產品時,需要編寫新的具體產品類靠益,而且還要提供與之對應的具體工廠類丧肴,系統中類的個數將成對增加,在一定程度上增加了系統的復雜度捆毫,有更多的類需要編譯和運行闪湾,會給系統帶來一些額外的開銷。
(2)由于考慮到系統的可擴展性绩卤,需要引入抽象層途样,在客戶端代碼中均使用抽象層進行定義江醇,增加了系統的抽象性和理解難度,且在實現時可能需要用到DOM何暇、反射等技術陶夜,增加了系統的實現難度。
角色
抽象工廠角色--- Factory
這是工廠方法模式的核心裆站,它與應用程序無關条辟。是具體工廠角色必須實現的接口或者必須繼承的父類
具體工廠角色--- Concrete Factory
它含有和具體業(yè)務邏輯有關的代碼。由應用程序調用以創(chuàng)建對應的具體產品的對象
抽象產品角色---Product
它是具體產品繼承的父類或者是實現的接口
具體產品角色---ConcreteProduct
具體工廠角色所創(chuàng)建的對象就是此角色的實例
適用場合
- 一個類不知道它所需要的對象的類:
在工廠方法模式中宏胯,客戶端不需要知道具體產品類的類名羽嫡,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創(chuàng)建肩袍;客戶端需要知道創(chuàng)建具體產品的工廠類 - 一個類通過其子類來指定創(chuàng)建哪個對象:
在工廠方法模式中杭棵,對于抽象工廠類只需要提供一個創(chuàng)建產品的接口,而由其子類來確定具體要創(chuàng)建的對象氛赐,利用面向對象的多態(tài)性和里氏 - 可配置
將創(chuàng)建對象的任務委托給多個工廠子類中的某一個魂爪,客戶端在使用時可以無需關心是哪一個工廠子類創(chuàng)建產品子類,需要時再動態(tài)指定艰管,可將具體工廠類的類名存儲在配置文件或數據庫中
一個例子
- 產品接口類:Product.java
public interface Product {
}
- 具體的產品類:Iphone.java
public class Iphone implements Product {
public Iphone() {
System.out.println("Iphone被制造了");
}
}
- 具體的產品類:Ipad.java
public class Ipad implements Product {
public Ipad() {
System.out.println("Ipad被制造了");
}
}
- 抽象工廠:Factory.java
public interface Factory {
public Product produce();
}
- 具體工廠類:IphoneFactory.java
public class IphoneFactory implements Factory {
@Override
public Product produce() {
return new Iphone();
}
}
- 具體工廠類:IpadFactory.java
public class IpadFactory implements Factory {
@Override
public Product produce() {
return new Ipad();
}
}
- 客戶端調用工廠創(chuàng)建產品:Client.java
public class Client {
public static void main(String[] args) {
Factory f1 = new IphoneFactory();
f1.produce();
Factory f2 = new IpadFactory();
f2.produce();
}
}
參考文章
創(chuàng)建對象與使用對象——談談工廠的作用
深入理解工廠模式
JAVA設計模式之工廠模式(簡單工廠模式+工廠方法模式)
關于 Java 的靜態(tài)工廠方法滓侍,看這一篇就夠了!
JAVA設計模式之 簡單工廠模式【Simple Factory Pattern】