設計模式: 工廠模式
技術標籤:設計模式
設計模式: 工廠模式
工廠是每一個開發人員應該知道的關鍵創造模式之一。它們是許多高階模式的主要組成部分。很長一段時間,我對不同型別的工廠模式感到困擾。此外,在同一篇文章中很難找到關於這些型別的資訊。本文介紹了四種工廠模式:
- 工廠方法模式
- 抽象工廠模式
- 靜態工廠方法
- 簡易工廠
“四人幫”在《設計模式: 可複用面向物件軟體的基礎》一書中對工廠方法模式進行了描述。當我第一次讀到這個模式時,我用靜態模式誤解了它,這是由 Java api 的主要設計師之一 Joshua Bloch 在他的書“ Effective Java”中描述的。這個簡單的工廠(有時稱為工廠)是非正式的,但是在網上出現了很多次。最後一種是抽象的工廠模式,在《四人幫》一書中也有描述,它是工廠方法模式的更廣泛概念。
在這篇文章中,我將解釋工廠有什麼用,然後我將用著名的Java框架或Java API的示例介紹每種型別。我將使用Java程式碼來實現工廠,但是如果您不瞭解Java,您仍然可以理解這個想法。此外,我將使用UML正式描述模式。
反面教材
儘管本文是關於工廠模式的,但僅僅為了使用模式而使用模式比從來不使用模式更糟糕。這種行為是一種反模式。實際上,大多數模式使程式碼更難理解。大多數時候,我不用工廠。例如:
- 不會改變很多的小專案
- 涉及多個開發人員使用相同程式碼的大中型專案,我發現它們很有用
我認為工廠模式是在它們的優勢[^將在下一部分中看到它們]以及程式碼的可讀性和理解性之間的權衡。
工廠的主要目標是實現物件,但是為什麼不直接用建構函式呼叫建立物件呢?
對於簡單的用例,不需要使用工廠。
@Data
@AllArgsConstructor
public class Simple {
private final Integer parameter1;
private final Integer parameter2;
}
public class SimpleFactory {
/**
* 用來建立物件的工廠方法
*/
public static Simple createFactory() {
return new Simple(1, 2);
}
public static void main(String[] args) {
// 得到工廠建立的物件
System.out.println(createFactory());
// TODO 你的業務程式碼
}
}
在這段程式碼中,SimpleClass 是一個非常簡單的類,具有狀態、沒有依賴性、沒有多型性、沒有業務邏輯。您可以使用工廠來建立這個物件,但是它將使程式碼量翻倍。因此,這會使程式碼更難理解。如果你能避免使用工廠來做這件事,你最終會得到一個更簡單的程式碼!
但是,在編寫需要許多開發人員和許多程式碼更改的大型應用程式時,您經常會遇到更復雜的情況。對於這些複雜的情況,工廠的優勢取代
了它們的劣勢。
對工廠的需求
既然我已經警告過你工廠的用途,讓我們來看看為什麼它們如此強大,因此在大多數專案中得到了應用。
靜態工廠
企業級應用程式的一個常見用例是限制一個類的數量。如何設法只擁有一個(或2個或10個)類例項,因為它消耗了諸如套接字、資料庫連線或檔案系統描述符等資源?
使用建構函式的方法,不同的函式(來自不同的類)很難知道一個類的例項是否已經存在。而且,即使有一個例項,一個函式怎麼能得到這個例項呢?你可以通過使用每個函式都會檢查的共享變數來做到這一點
- 它將連結所有的函式的行為,因為它們使用和修改相同的共享變數,所以需要在同一個類中實現。
- 程式碼的多個部分將具有相同的邏輯來檢查類是否已經被即時化,這將導致程式碼重複(非常糟糕!)
使用靜態工廠方法,你可以很容易做到這一點:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
public class SingletonTest1{
public static void getInstance(){
// 獲取例項物件
Singleton instance = Singleton.getInstance();
// TODO 其他業務程式碼
}
}
public class SingletonTest2{
public static void getInstance(){
// 獲取例項物件
Singleton instance = Singleton.getInstance();
// TODO 其他業務程式碼
}
}
在這段程式碼中,我們使用的工廠將 Singleton 類的例項數限制為一個。通過限制物件的數量,我們建立了一個例項池,這個例項池基於一個工廠。
注意: 我們可以修改一個例項的建立方式,而不是限制數量,例如使用一個原型模式,而不是每次都從頭建立一個新物件。
鬆散耦合
工廠的另一個優點是鬆耦合。
假設您編寫了一個計算東西並需要寫日誌的程式。由於這是一個大專案,所以在編寫業務類時,您的同事之後會對將日誌寫入檔案系統(FileSystemLogger 類)的類進行編碼。在沒有工廠的情況下,在使用 FileSystemLogger 之前,你需要在一個建構函式中註明 FileSystemLogger:
public class FileSystemLogger {
public void writeLog(String s) {
System.out.println("日誌資訊為:" + s);
// TODO 其他業務程式碼
}
public static void main(String[] args) {
FileSystemLogger logger = new FileSystemLogger();
logger.writeLog("這是一個日誌");
}
}
但是,如果突然發生變化,您現在需要使用實現的 DatabaseLogger 在資料庫中寫入日誌,那會發生什麼呢?如果沒有工廠,則必須使用 FileSystemLogger類修改所有函式。因為這個記錄器無處不在,你需要修改數百個函式/類,而使用工廠,你只需修改工廠就可以輕鬆地從一個實現切換到另一個實現:
public interface Logger {
/**
* 輸出日誌
*
* @param s 日誌資訊
*/
void writeLog(String s);
}
/**
* 系統日誌
*/
class SystemLogger implements Logger {
@Override
public void writeLog(String s) {
System.out.println("系統日誌" + s);
}
}
/**
* 資料庫日誌
*/
class DatabaseLogger implements Logger {
@Override
public void writeLog(String s) {
System.out.println("資料庫日誌" + s);
}
}
/***
* 工廠類,建立對應的物件
*/
class FactoryLogger {
public static Logger createLogger() {
// TODO 根據具體業務 您可以選擇對應的日誌類 只要它是一個ILogger
return new SystemLogger();
}
}
class SomeClass {
public static void main(String[] args) {
// 如果工廠類的實現改變,客戶端的程式碼也不需要有任何的變化
Logger logger = FactoryLogger.createLogger();
logger.writeLog("這是一個日誌");
}
}
如果檢視這段程式碼,您可以輕鬆地將日誌記錄器實現從 SystemLogger更改為 DatabaseLogger。您只需要修改 createLogger ()函式(這是一個工廠)。對於客戶機(業務)程式碼來說,這種更改是不可見的,因為客戶機程式碼使用了 Logger的介面,而記錄器實現的選擇是由工廠決定的。通過這樣做,您將在日誌記錄器的實現和使用日誌記錄器的程式碼部分之間建立鬆散耦合。
封裝
有時,使用工廠可以提高程式碼的可讀性,並通過封裝降低其複雜性。
假設您需要使用一個比較兩輛汽車特性的類 CarComparator。這個類需要一個 DatabaseConnection 來獲取數百萬輛汽車的特性,並需要一個 FileSystemConnection 來獲取引數化比較演算法的配置檔案(例如: 增加的燃料消耗量大於最大速度)。如果沒有工廠,你可以編寫這樣的程式碼:
public class DatabaseConnection {
DatabaseConnection(String parameters) {
System.out.println("從資料庫獲取數百萬輛汽車的特性");
// TODO
}
}
class FileSystemConnection {
FileSystemConnection(String parameters) {
System.out.println("讀取配置檔案的程式碼");
// TODO
}
}
/**
* 比較兩輛汽車的特性
*/
class CarComparator {
CarComparator(DatabaseConnection dbConn, FileSystemConnection fsConn) {
// TODO
}
public int compare(String car1, String car2) {
// TODO 業務邏輯程式碼
return 1;
}
}
class CarBusinessXY {
public static void main(String[] args) {
DatabaseConnection db = new DatabaseConnection("資料庫連線");
FileSystemConnection fs = new FileSystemConnection("配置檔案地址");
CarComparator carComparator = new CarComparator(db, fs);
carComparator.compare("福特野馬5.0T","道奇挑戰者");
}
}
class CarBusinessZY {
public static void main(String[] args) {
DatabaseConnection db = new DatabaseConnection("資料庫連線");
FileSystemConnection fs = new FileSystemConnection("配置檔案地址");
CarComparator carComparator = new CarComparator(db, fs);
carComparator.compare("保時捷918", "法拉利拉法");
}
}
這段程式碼可以正常工作,但是您可以看到,為了使用比較方法,您需要稍加留意
- 資料庫連線
- 一個檔案系統連線
- 然後是 CarComparator
如果您需要在多個函式中使用比較,那麼您必須複製您的程式碼,這意味著如果 CarComparator 的結構發生變化,您必須修改所有重複的部分。使用 factory 可以對程式碼進行因數分解,並隱藏 CarComparator 類構造的複雜性。
// 使用工廠重構
public class Factory {
public static CarComparator getCarComparator() {
DatabaseConnection db = new DatabaseConnection("資料庫連線");
FileSystemConnection fs = new FileSystemConnection("配置檔案地址");
CarComparator carComparator = new CarComparator(db, fs);
}
}
// 使用工廠的示例程式碼
class CarBusinessXxx {
public static void main(String[] args) {
CarComparator carComparator = Factory.getCarComparator();
carComparator.compare("福特野馬5.0T","道奇挑戰者");
}
}
// 使用工廠的示例程式碼
class CarBusinessYyy {
public static void main(String[] args) {
CarComparator carComparator = Factory.getCarComparator();
carComparator.compare("保時捷918", "法拉利拉法");
}
}
如果你比較這兩個程式碼,你可以看到使用一個工廠:
- 減少程式碼行數
- 避免程式碼重複
- 組織程式碼: 使用工廠構建一個CarComparator,只是使用它的公共方法。
最後一點很重要 (事實上,它們都很重要 ) ,因為這是關於關注點分離的。業務類不應該知道如何構建它需要使用的複雜物件, 業務類只需要關注業務。此外,它還增加了同一個專案的開發人員之間的分工:
- 一個在 CarComparator 和它的建立方式上工作
- 其他人則處理使用 CarComparator 的業務物件
消除歧義
假設您有一個具有多個建構函式(具有非常不同的行為)的類。如何確保不會錯誤地使用錯誤的建構函式?讓我們來看看下面的程式碼:
class Example{
// 建構函式 1
public Example(double a, float b) {
// ···
}
// 建構函式 2
public Example(double a) {
// ···
}
// 建構函式 3
public Example(float a, double b) {
// ···
}
}
雖然第一個和第二個建構函式的引數數量不一樣,但是你可能很快就無法選擇正確的引數,特別是在你忙碌了一天之後,使用你最喜歡的 IDE的自動補全方法。更難看出建構函式1和建構函式3之間的區別。這個示例看起來像一個假的,但是我在專案的遺留程式碼中看到了它(真實故事!).問題是,如何使用相同型別的引數實現不同的建構函式(同時避免使用類似於建構函式1和3的骯髒方式) ?
下面是一個使用工廠的解決方案:
public class Complex {
public static Complex fromCartesian(double real, double imag) {
return new Complex(real, imag);
}
public static Complex fromPolar(double rho, double theta) {
return new Complex(rho * Math.cos(theta), rho * Math.sin(theta));
}
// 更多的方法
private Complex(double a, double b) {
// ···
}
}
在這個例子中,使用工廠添加了關於建立內容的描述,使用工廠方法名稱: 您可以從笛卡爾座標系或極座標系建立一個複數。在這兩種情況下,你確切地知道創造是關於什麼的。
工廠模式
現在我們看到了工廠的利弊,讓我們關注不同型別的工廠模式。
我將介紹從最簡單到最抽象的每一個工廠。如果你想使用工廠,記住越簡單越好。
靜態工廠方法
注意: 如果您閱讀了本文並且對 Java 瞭解不多,那麼靜態方法就是類方法。
Joshua Bloch 在《Effective Java 》一書中描述了靜態工廠方法:
一個類可以提供一個公共靜態工廠方法,這是一個簡單的靜態方法,它返回一個類的例項。
換句話說,類可以提供返回例項的靜態方法,而不是使用建構函式來建立例項。如果此類具有子型別,則靜態工廠方法可以返回該類或其子型別的型別。雖然我討厭 UML,但是我在文章的開頭說過,我將使用 UML 給出一個正式的描述。下面就是:
在這個關係圖中,ObjectWithStaticFactory 類有一個靜態工廠方法(稱為 getObject ())。這個方法可以識別任何型別的 ObjectWithStaticFactory,這意味著型別 ObjectWithStaticFactory 或型別 SubType1或型別 SubType2。當然,這個類可以有其他方法、屬性和靜態工廠方法。
讓我們來看看這段程式碼:
public class MyClass {
Integer a;
Integer b;
MyClass(int a, int b){
this.a=a;
this.b=b;
};
public static MyClass getInstance(int a, int b){
return new MyClass(a, b);
}
public static void main(String[] args){
// 使用建構函式例項化
MyClass a = new MyClass(1, 2);
// 使用靜態工廠方法例項化
MyClass b = MyClass.getInstance(1, 2);
}
}
這段程式碼展示了建立MyClass例項的兩種方法:
- MyClass 中的靜態工廠方法 getInstance ()
- MyClass 的建構函式
但是這個概念可以更深入。如果一個具有靜態工廠方法的類可以實時儲存另一個類,那該怎麼辦?約書亞 · 布洛赫描述了這種可能性:
介面不能有靜態方法,因此根據約定,名為 Type 的介面的靜態工廠方法放在名為 Types 的不可例項化類
在這種情況下,factory 方法 getObject 位於抽象類名 Types 中。Factory 方法可以建立類 Type 的例項或類 Type 的任何子型別(圖中的 SubType1或 SubType2)。getObject ()方法可以有引數,以便為給定的引數返回 SubType1,否則返回 SubType2。
讓我們回到 Java,假設我們有兩個類: Ferrari 和 Mustang,它們實現了一個介面 Car。靜態工廠方法可以放在一個名為“ CarFactory”的抽象類中(遵循Joshua Boch 的約定,該類的名稱應該是“ Cars”,但我不喜歡它) :
public interface Car {
/**
* 造車
*/
void build();
}
class Mustang implements Car {
@Override
public void build() {
System.out.println("這是一輛野馬");
}
}
class Ferrari implements Car {
@Override
public void build() {
System.out.println("這是一輛法拉利");
}
}
/**
* 抽象類,不可例項化
*/
abstract class CarFactory {
// 工廠方法,造出不同的車來
public static Car getCar() {
// TODO 選擇你想要的車(你的邏輯···)
return new Ferrari();
}
}
/**
* 使用工廠示例
*/
class Employ {
public static void main(String[] args) {
Car myCar = CarFactory.getCar();
myCar.build();
}
}
與其他工廠模式相比,這種模式的強大之處在於您不需要
- 例項化工廠以便使用它(幾分鐘後您就會明白我的意思)
- 工廠實現一個介面。
它易於使用,但僅適用於提供類方法 ( 即 static java 關鍵字修飾)。
注意:關於工廠,網路上的許多帖子都是錯誤的 ,例如stackoverflow上的該帖子被提高了1.5k次。給定的工廠方法模式示例的問題在於它們是靜態工廠方法。如果我引用 約書亞·布洛赫(Joshua Bloch)的話:
靜態工廠方法不同於設計模式中的工廠方法模式。本文中描述的靜態工廠方法在設計模式中沒有直接等價物
如果您檢視 stackoverflow 文章,那麼只有最後一個示例URLStreamHandlerFactory
是 GoF 的工廠方法模式(我們將在幾分鐘後看到這個模式)
真實的例子
下面是 Java 框架和 Java API 中靜態工廠方法的一些示例。在 Java API 中找到示例非常容易,因為 Joshua Bloch 是許多 Java API 的主要架構師。
日誌框架(Logging frameworks)
Java 日誌框架 slf4j、 logback 和 log4j 使用一個抽象類 LoggerFactory
。如果開發人員想寫日誌,他需要從 LoggerFactory
的靜態方法 getLogger ()
中獲得 Logger 的例項。getLogger ()
返回的 Logger 實現將取決於 getLogger ()
的實現以及getLogger ()
使用的開發人員所寫的配置檔案。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Example{
public void example() {
// 使用靜態工廠方法來獲得Logger的例項
Logger logger = LoggerFactory.getLogger(Example.class);
logger.info("這是一個例子");
}
}
注意: 無論使用 slf4j、 log4j 還是 slf4j,工廠類的名稱及其靜態工廠方法都不完全相同。
Java String class
Java 中的 String 類表示一個字串。有時,您需要從布林值或整數獲取字串。但是 String 不提供像 String (Integer i)或 String (Boolean b)這樣的建構函式。相反,它提供了多個靜態工廠方法 String.valueOf (…)
int` `i = ``12``;
String integerAsString = String.valueOf(i);
簡易工廠
這種模式不是“真實的”,但是我在網上看到過很多次。它沒有一個正式的描述,但這裡是我的: 一個簡單的工廠(或工廠)是一個工具
- 他們的工作是建立/實時化物件
- 既不是工廠方法模式
我們將在後面看到這個模式
- 也不是一個抽象的工廠模式
我們將在後面看到這個模式
您可以看到它具有靜態工廠模式的一般化,但是這一次可以暫存(或不暫存)工廠,因為“工廠方法”不是一個類方法(但是它可以)。對於 Java 開發人員來說,使用非靜態形式的簡單工廠是很少見的。因此,這種模式在大多數情況下等價於靜態模式。下面是非靜態形式的 UML:
在這種情況下,Factory 方法 getObject ()位於名為 Factory 的類中。Factory 方法不是 class 方法,因此在使用它之前需要先了解 Factory。工廠方法可以建立類 Type 或其任何子型別的例項。
下面是來自靜態工廠方法的前一個示例,但是這一次我在使用它之前先對工廠進行了檢查
public interface Car {
/**
* 造車
*/
void build();
}
class Mustang implements Car {
@Override
public void build() {
System.out.println("這是一輛野馬");
}
}
class Ferrari implements Car {
@Override
public void build() {
System.out.println("這是一輛法拉利");
}
}
/**
* 工廠方法,造出不同的車來
*/
class CarFactory {
/**
* 這個類是可例項化的
*/
public CarFactory() {
// TODO
}
public static Car getCar() {
// TODO 選擇你想要的車(你的邏輯···)
return new Ferrari();
}
}
/**
* 使用工廠示例
*/
class Employ {
public static void main(String[] args) {
CarFactory carFactory = new CarFactory();
Car myCar = carFactory.getCar();
myCar.build();
}
}
如你所見,這次我需要留意工廠以便使用它。我沒有在 Java 中找到真正的例子,因為使用靜態工廠方法比使用簡單的工廠更好。不過,如果您的工廠方法需要一些例項才能工作,那麼您可以在其非靜態形式中使用此模式。例如,如果您需要一個數據庫連線,那麼您可以首先實現您的工廠(這將實現資料庫連線) ,然後使用需要這個連線的工廠方法。就個人而言,在這種情況下,我仍然會使用帶有惰性初始模式的靜態工廠(以及一個數據庫連線池)。
告訴我您是否知道一個 Java 框架,它以非靜態的形式使用簡單的工廠。
工廠方法模式
工廠方法模式是一個更抽象的工廠。以下是“四人幫”給出的定義:
定義用於建立物件的介面,但讓子類決定例項化哪個類。工廠方法允許類將例項化推遲到子類
下面是工廠方法模式的簡化 UML 圖:
這個圖看起來像簡單的工廠圖以非靜態形式
。唯一的也是最大的
不同之處在於介面工廠:
- 工廠代表“用於建立物件的介面”。它描述了一種工廠方法:getObjects()
- ConcreteFactory 代表“決定例項化哪個類的子類之一”。每個 ConcreteFactory 都有自己的工廠方法 getObjects ()的實現
在關係圖中,getObjects ()必須返回一個 Type (或其子型別)。這意味著一個 contrete factory 可以返回 Subtype1,而另一個可以返回 SubType2。
為什麼使用工廠方法模式而不是簡單的工廠?
僅當您的程式碼需要多個工廠實現時。這將迫使每個工廠實現具有相同的邏輯,以便使用一個實現的開發人員可以輕鬆地切換到另一個實現,而不必擔心如何使用它(因為他只需呼叫具有相同簽名的工廠方法)。
由於這是抽象的,讓我們回到汽車的例子。這不是一個很好的例子,但是我使用它是為了讓你看到簡單工廠的不同之處(我們將看到真實的示例以瞭解此模式的強大功能) :
public interface Car {
/**
* 造車
*/
void build();
}
class Mustang implements Car {
@Override
public void build() {
System.out.println("這是一輛野馬");
}
}
class Ferrari implements Car {
@Override
public void build() {
System.out.println("這是一輛法拉利");
}
}
/**
* 工廠方法,造出不同的車來
*/
interface CarFactory {
Car getCar();
}
/**
* 具有getCar()工廠方法實現的真實工廠
*/
class ConcreteCarFactory implements CarFactory{
// 這個類是可例項化的
public ConcreteCarFactory(){
// TODO
}
@Override
public Car getCar() {
// TODO 選擇你想要的車(你的邏輯···)
return new Ferrari();
}
}
/**
* 使用工廠示例
*/
class Employ {
public static void main(String[] args) {
CarFactory carFactory = new ConcreteCarFactory();
Car myCar = carFactory.getCar();
myCar.build();
}
}
如果將此程式碼與簡單工廠進行比較,這次我添加了一個介面(CarFactory)。真正的工廠(ConcreteCarFactory)實現了這個介面。
正如我說的那樣,這不是一個很好的示例,因為在此示例中,您不應該使用工廠方法模式,因為 只有一個具體的工廠。 這將是唯一的,如果我有多個實現像有用SportCarFactory,VintageCarFactory,LuxeCarFactory,CheapCarFactory …。在這種情況下,由於工廠方法始終是getCar(),因此開發人員可以輕鬆地從一種實現切換到另一種實現。
真實的例子
在 Java中,一個常見的例子是集合 API 中的 Iterable()函式。每個集合都實現了介面 Iterable < e > 。這個介面描述了一個函式迭代器() ,它返回一個迭代器 < e > 。陣列列表 < e > 是一個集合。因此,它實現了介面 Iterable < e > 和它的工廠方法 Iterator () ,後者返回 Iterator < e > 的子類
// 下面是來自java原始碼的迭代器的簡化定義
public interface Iterator<E> {
// 如果迭代具有更多元素,則返回 true
boolean hasNext();
// 返回迭代中的下一個元素
E next();
// 從底層集合中刪除此迭代器返回的最後一個元素
void remove();
}
// 工廠介面
public interface Iterable<T> {
Iterator<T> iterator();
}
// 下面是java原始碼中對ArrayList的簡化定義
// 你可以看到這個類是一個實現的具體工廠
// 工廠方法迭代器()
// 注意:在實際的Java原始碼中,ArrayList是由
// AbstractList,它使用工廠實現了工廠方法模式
public class ArrayList<E> {
// iterator()返回一個子型別和一個“匿名”迭代器<E>
public Iterator<E> iterator()
{
return new Iterator<E>()
{
// 實現方法hasNext(), next()和remove()
};
}
}
下面是 ArrayList 的一個標準用法
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Example {
public static void main(String[] ars){
// 例項化(具體工廠)
List<Integer> myArrayList = new ArrayList<>();
// 呼叫ArrayList的工廠方法iterator()
Iterator<Integer> myIterator = myArrayList.iterator();
}
}
我展示了一個ArrayList,但是我可以展示HashSet,LinkedList或HashMap,因為它們都是集合API的一部分。這種模式的優勢在於您不需要知道您使用的是哪種型別的集合,每個集合都將通過工廠方法iterator()提供一個Iterator。
另一個很好的例子是 Java 8 CollectionAPI中的stream()
方法。
SPring
Spring 框架基於工廠方法模式。ApplicationContext 實現 BeanFactory 介面。此介面描述返回 Object 的函式 Object getBean (param)。這個例子很有趣,因為 java 中的每個 Class 都是從 Object 派生的。因此,這個工廠可以返回任何類的例項取決於引數
。
public class Example{
public static void main(String[] args) {
// 建立BeanFactory
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
// 用工廠建立完全不同型別的物件
MyType1 objectType1 = context.getBean("myType1");
MyType2 objectType2 = context.getBean("myType2");
}
}
The abstract factory 抽象工廠
四人幫這樣描述這家工廠:提供一個介面,用於建立相關或依賴物件的系列,而不指定它們的具體類
如果你不理解這個句子,不要擔心,這是正常的。它不是無緣無故被稱為抽象工廠!
如果它可以幫助您,我將抽象工廠模式看作是工廠方法模式的泛化,這次工廠介面有多個相關的工廠方法。當我說相關的時候,我的意思是概念上的聯絡,這樣它們就形成了一個工廠方法的“家庭”。讓我們看看 UML 圖,看看它與工廠方法模式的區別:
- Factory是定義多個工廠方法的介面,在我們的示例中為:getObject1()和getObject2()。每個方法都會建立一個型別(或其子型別)。
- ConcreteFactory實現Factory介面,因此具有自己的getObject1()和getObject2()實現。現在假設有兩個具體工廠:一個可以返回
SubType1.1
和SubType2.1
的例項,另一個可以返回SubType1.2
和SubType2.2
的例項。
由於這是非常抽象的,讓我們回到 CarFactory 示例。
使用工廠方法模式,工廠介面只有一種方法getCar()
。抽象工廠可以是具有3種工廠方法的介面:getEngine()
,getBody()
和getWheel()
。您可能有多個具體工廠:
- SportCarFactory 可以返回PowerfulEngine,RaceCarBody和RaceCarWheel的例項
- CheapCarFactory 可以返回WeakEngine,HorribleBody和RottenWheel的例項
如果要製造跑車,則需要例項化SportCarFactory然後使用它。而且,如果您想製造一輛廉價汽車,則需要例項化CheapCarFactory然後使用它。
這個抽象工廠的3種工廠方法是相關的。它們都屬於汽車生產概念。
當然,工廠方法可以具有引數,以便它們返回不同的型別。例如,SportCarFactory的 getEngine(String model)工廠可以根據引數返回Ferrari458Engine或FerrariF50Engine或Ferrari450Engine或···。
下面是 Java中的示例只有 SportCarFactory 和2個 factory 方法
。
/**
* 車輪、方向盤
*/
public interface Wheel {
void turn();
}
class RaceCarWheel implements Wheel {
@Override
public void turn() {
System.out.println("製造車輪,方向盤,車身等");
}
}
/**
* 發動機
*/
interface Engine {
void work();
}
class PowerfulEngine implements Engine {
@Override
public void work() {
System.out.println("製造一個發動機");
}
}
/**
* 工廠
*/
interface CarFactory {
// 返回車子的發動機
Engine getEngine();
// 返回車子其他部分
Wheel getWheel();
}
class SportCarFactory implements CarFactory {
@Override
public Engine getEngine() {
// 製造一個發動機
return new PowerfulEngine();
}
@Override
public Wheel getWheel() {
// 製造車子其他部分
return new RaceCarWheel();
}
}
class SomeClass {
public static void main(String[] args) {
CarFactory carFactory = new SportCarFactory();
Wheel myWheel= carFactory.getWheel();
myWheel.turn();
Engine myEngine = carFactory.getEngine();
myEngine.work();
}
}
這個工廠不容易,你什麼時候用呢?
~~決不!!!!~~哼,很難回答。我將這種工廠模式視為組織程式碼的一種方式。如果您在程式碼中得到許多工廠方法模式,並且看到了它們之間的共同主題,則可以使用抽象工廠來收集該組。我不贊成“讓我們使用抽象工廠,因為將來我們可能需要一個抽象工廠”,因為這種模式非常抽象。我更喜歡構建簡單的東西,並在需要後對其進行重構。
但是,常見的用例是需要建立外觀和感覺不同的使用者介面。這個例子被“四人幫”用來呈現這種模式。該UI需要一些產品,例如視窗,滾動條,按鈕…您可以為每種外觀建立帶有具體工廠的Factory。當然,此示例是在Internet時代之前編寫的,現在您甚至可以為桌面應用程式使用CSS(或某些指令碼語言)來擁有一個元件並修改其外觀。這意味著大多數情況下,靜態工廠方法就足夠了。
但是,如果您仍然想使用此模式,則可以使用GoFGang of Four的簡稱 -> 設計模式:可複用面向物件軟體的基礎 的作者
中的一些用例:
“系統應配置有多個產品系列之一”
“您想提供產品的類庫,並且只想顯示它們的介面,而不是它們的實現”
真實的例子
大多數DAO(資料訪問物件)框架使用抽象工廠來指定具體工廠應該執行的基本操作。儘管工廠方法的名稱取決於框架,但通常不適合:
- createObject(…)或persistObject(…)
- updateObject(…)或saveObject(…)
- deleteObject(…)或removeObject(…)
- readObject(…)或findObject(…)
對於您處理的每種型別的物件,您都需要一個具體的工廠。例如,如果您需要使用資料庫來管理人員,房屋和合同。我將有一個PersonFactory,一個HouseFactory和一個ContractFactory。
Spring的CrudRepository是抽象工廠的一個很好的例子。
如果您需要Java程式碼,則可以查詢JPA,Hibernate或SpringData教程。
結論
我希望您現在對不同型別的工廠模式以及何時使用它們有所瞭解。儘管我在本文中多次說過,但請記住,大多數情況下,工廠會使程式碼更復雜/更抽象。即使您瞭解工廠(如果您不知道,請再次閱讀本文!),您的同事又如何呢?但是,在中型/大型應用程式上工作時,值得使用工廠。
我在工廠苦苦掙扎了很長時間,以瞭解是哪一個,像這樣的一篇文章對我有幫助。我希望本文是可以理解的,並且我沒有寫太多錯誤。隨時告訴我您閱讀的內容是否困擾您,以便我改善本文。