最簡單的設計模式,抽象工廠模式,是否屬于過度設計?
在日常開發中,我們往往忽視了設計模式的重要性。這可能是因為項目時間緊迫,或者對設計模式理解不深。其實,很多時候我們可能在不經意間已經使用了某些模式。
重要的是要有意識地學習和應用,讓代碼更加優雅和高效。也許是時候重新審視我們的編程實踐,將設計模式融入其中了。
先從最簡單的設計模式【抽象工廠模式】走起,讓我們一起“重學設計模式”。
靈魂拷問:新增具體產品時,擴展新的工廠類再在客戶端中創建工廠類代碼 和 在客戶端中直接創建實例代碼,有什么本質區別嗎?
一、不采用抽象工廠模式
假設你正在設計一套系統,該系統用于創建不同配置的電腦,例如PC和服務器。每種類型的電腦都有不同的CPU和內存。
1.抽象產品
/**
* 抽象產品 - CPU
*/
public interface CPU {
void compute();
}
/**
* 抽象產品 - 內存
*/
public interface Memory {
void store();
}
2.具體產品
/**
* 具體產品 - PC 的 CPU 實現
*/
public class PCCPU implements CPU {
@Override
public void compute() {
System.out.println("PC CPU is computing");
}
}
/**
* 具體產品 - PC 的內存實現
*/
public class PCMemory implements Memory {
@Override
public void store() {
System.out.println("PC Memory is storing data");
}
}
/**
* 具體產品 - 服務器的 CPU 實現
*/
public class ServerCPU implements CPU {
@Override
public void compute() {
System.out.println("Server CPU is computing with high performance");
}
}
/**
* 具體產品 - 服務器的內存實現
*/
public class ServerMemory implements Memory {
@Override
public void store() {
System.out.println("Server Memory is storing data with high capacity");
}
}
3.測試類 ~ 不用抽象工廠模式
/**
* 不用抽象工廠模式
*/
public class Client {
public static void main(String[] args) {
// 創建 PC 的組件
CPU pcCPU = new PCCPU();
Memory pcMemory = new PCMemory();
pcCPU.compute();
pcMemory.store();
// 創建 服務器 的組件
CPU serverCPU = new ServerCPU();
Memory serverMemory = new ServerMemory();
serverCPU.compute();
serverMemory.store();
}
}
4.新增筆記本的CPU和內存
/**
* 具體產品 - 新增筆記本電腦的 CPU 實現
*/
public class LaptopCPU implements CPU {
@Override
public void compute() {
System.out.println("Laptop CPU is computing efficiently");
}
}
/**
* 具體產品 - 筆記本電腦的內存實現
*/
public class LaptopMemory implements Memory {
@Override
public void store() {
System.out.println("Laptop Memory is storing data efficiently");
}
}
5.在Client測試類中新增:
// 創建筆記本的
CPU laptopCPU = new LaptopCPU();
laptopCPU.compute();
LaptopMemory laptopMemory = new LaptopMemory();
laptopMemory.store();
還是很方便的。
二、那么,如果采用抽象工廠模式優化上面代碼,要怎么做呢?
抽象工廠模式(Abstract Factory Pattern)是一種創建型設計模式,旨在提供一個接口,用于創建相關或依賴對象的家族,而無需指定它們的具體類。通過抽象工廠模式,客戶端可以通過接口創建一系列相關對象,而無需知道這些對象的具體實現。
抽象工廠模式通常包括以下幾個部分:
- 抽象工廠接口(Abstract Factory):定義創建產品的接口。
- 具體工廠類(Concrete Factory):實現抽象工廠接口,生成具體的產品。
- 抽象產品接口(Abstract Product):定義產品的接口。
- 具體產品類(Concrete Product):實現具體的產品。
- 客戶端(Client):通過工廠接口來使用產品,而不依賴于具體產品的實現。
1.抽象工廠接口
/**
* 抽象工廠
*/
public interface ComputerFactory {
CPU createCPU();
Memory createMemory();
}
2.具體工廠類
/**
* 具體工廠 - PC 工廠
*/
public class PCFactory implements ComputerFactory {
@Override
public CPU createCPU() {
return new PCCPU();
}
@Override
public Memory createMemory() {
return new PCMemory();
}
}
/**
* 具體工廠 - 服務器工廠
*/
public class ServerFactory implements ComputerFactory {
@Override
public CPU createCPU() {
return new ServerCPU();
}
@Override
public Memory createMemory() {
return new ServerMemory();
}
}
3.測試類 ~ 抽象工廠模式
public class AbstractFactoryClient {
public static void main(String[] args) {
// 使用 PC 工廠創建 PC 組件
ComputerFactory pcFactory = new PCFactory();
CPU pcCPU = pcFactory.createCPU();
Memory pcMemory = pcFactory.createMemory();
pcCPU.compute();
pcMemory.store();
// 使用服務器工廠創建服務器組件
ComputerFactory serverFactory = new ServerFactory();
CPU serverCPU = serverFactory.createCPU();
Memory serverMemory = serverFactory.createMemory();
serverCPU.compute();
serverMemory.store();
}
}
使用抽象工廠模式時,新增筆記本電腦,如何實現?
4.新增筆記本電腦的具體工廠類
public class LaptopFactory implements ComputerFactory {
@Override
public CPU createCPU() {
return new LaptopCPU();
}
@Override
public Memory createMemory() {
return new LaptopMemory();
}
}
5.在測試類中新增
// 使用筆記本工廠
ComputerFactory laptopFactory = new LaptopFactory();
CPU laptopCPU = laptopFactory.createCPU();
Memory laptopMemory = laptopFactory.createMemory();
laptopCPU.compute();
laptopMemory.store();
三、新增具體產品時,擴展新的工廠類再在客戶端中創建工廠類代碼 和 在客戶端中直接創建實例代碼,有什么本質區別嗎?
1.解耦
問題: 在不使用抽象工廠模式的實現中,客戶端代碼(Client)直接依賴于具體的產品類(如 PCCPU、PCMemory、ServerCPU 等)。當需要增加新類型的電腦或更改現有的電腦配置時,客戶端代碼必須修改。
抽象工廠模式引入了工廠接口(ComputerFactory),客戶端通過工廠接口創建對象,而不是直接實例化具體類。這樣客戶端與具體的產品實現解耦,客戶端不需要知道具體的產品類名,只需要依賴工廠接口。代碼更加靈活,可以支持不同的產品家族(PC、服務器、筆記本、工作站等)而無需修改客戶端代碼。
產品創建邏輯都集中在具體工廠中,客戶端只關心如何使用產品,而不關心它們是如何創建的。
2.擴展性
問題: 如果要在不使用工廠模式的代碼中增加一個新產品(例如,平板電腦的CPU和內存),需要修改客戶端代碼,增加新產品的實例化邏輯。這違反了開閉原則(對擴展開放,對修改關閉)。
抽象工廠模式允許我們通過創建新的工廠(例如 LaptopFactory)來生成新產品,而無需修改客戶端代碼。只需在新工廠中提供相應的產品(CPU和Memory),然后將工廠傳遞給客戶端。
支持開閉原則:新增產品只需增加新的工廠類,不需要修改已有代碼,減少了風險和維護成本。
方便維護:如果產品發生變化(比如某種CPU規格變動),只需要在工廠中修改,不必修改所有調用代碼,系統的靈活性顯著提高。
3.單一職責原則
問題: 客戶端代碼不僅負責業務邏輯,還負責產品的創建邏輯,違反了單一職責原則。
在客戶端中創建工廠類:客戶端的職責更加單一,只負責業務邏輯處理,產品的創建則完全由工廠負責。通過將產品的創建與使用分離,代碼變得更加清晰、職責分明。
優點:遵循單一職責原則,產品創建邏輯集中在工廠中,客戶端只處理與產品的使用有關的業務邏輯。
四、在jdk源碼中,哪些地方應用了抽象工廠模式
在 JDK 的源碼中,抽象工廠模式被廣泛應用于創建不同類型的對象家族。
一個典型的例子是 javax.xml.parsers.DocumentBuilderFactory 和 javax.xml.parsers.SAXParserFactory,它們都是 JDK 中使用抽象工廠模式的具體實現。這些工廠用于創建與 XML 解析相關的對象,如 DocumentBuilder 和 SAXParser,而客戶端不需要關心這些對象的具體實現。
1.DocumentBuilderFactory 在 JDK 中的應用
(1)抽象工廠類:DocumentBuilderFactory
DocumentBuilderFactory 是一個抽象工廠,用于創建 DocumentBuilder,它負責解析 XML 文檔。
newInstance() 方法會返回一個具體的工廠實現(如 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl),該工廠實現負責創建具體的 DocumentBuilder 對象。
package javax.xml.parsers;
public abstract class DocumentBuilderFactory {
// 用于創建 DocumentBuilder 實例
public abstract DocumentBuilder newDocumentBuilder() throws ParserConfigurationException;
// 工廠方法,用于獲取 DocumentBuilderFactory 的實例
public static DocumentBuilderFactory newInstance() {
return FactoryFinder.find(DocumentBuilderFactory.class, "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
}
}
(2)具體工廠類:DocumentBuilderFactoryImpl
DocumentBuilderFactoryImpl 是 DocumentBuilderFactory 的一個具體實現,它定義了如何創建 DocumentBuilder。
newDocumentBuilder() 返回一個 DocumentBuilderImpl 實例,具體負責解析 XML。
package org.apache.xerces.jaxp;
public class DocumentBuilderFactoryImpl extends DocumentBuilderFactory {
@Override
public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
return new DocumentBuilderImpl();
}
}
(3)客戶端使用
在客戶端代碼中,使用 DocumentBuilderFactory 創建 DocumentBuilder 對象,而不需要關心具體實現。
通過 DocumentBuilderFactory.newInstance(),客戶端獲取了一個具體的 DocumentBuilderFactory 實例,然后使用該工廠創建 DocumentBuilder 對象。
客戶端不關心 DocumentBuilder 的具體實現,只依賴于抽象工廠接口,符合抽象工廠模式的設計思想。
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
public class XMLParserExample {
public static void main(String[] args) {
try {
// 獲取 DocumentBuilderFactory 實例(抽象工廠)
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 使用工廠創建 DocumentBuilder(具體產品)
DocumentBuilder builder = factory.newDocumentBuilder();
// 使用 DocumentBuilder 解析 XML 文檔
// 示例代碼忽略了具體 XML 文件的解析邏輯
System.out.println("DocumentBuilder created successfully!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.SAXParserFactory 的應用
SAXParserFactory 是 JDK 中另一個典型的抽象工廠模式實現,它用于創建 SAXParser 對象,解析 XML 文檔。
(1)抽象工廠類:SAXParserFactory
SAXParserFactory 是抽象工廠類,定義了創建 SAXParser 的方法。
package javax.xml.parsers;
public abstract class SAXParserFactory {
// 創建 SAXParser 的抽象方法
public abstract SAXParser newSAXParser() throws ParserConfigurationException, SAXException;
// 獲取 SAXParserFactory 實例
public static SAXParserFactory newInstance() {
return FactoryFinder.find(SAXParserFactory.class, "org.apache.xerces.jaxp.SAXParserFactoryImpl");
}
}
(2)具體工廠類:SAXParserFactoryImpl
SAXParserFactoryImpl 是 SAXParserFactory 的具體實現,負責創建 SAXParser。
package org.apache.xerces.jaxp;
public class SAXParserFactoryImpl extends SAXParserFactory {
@Override
public SAXParser newSAXParser() throws ParserConfigurationException, SAXException {
return new SAXParserImpl();
}
}
(3)客戶端使用
客戶端通過 SAXParserFactory 來創建 SAXParser,解析 XML 文檔。
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
public class SAXParserExample {
public static void main(String[] args) {
try {
// 獲取 SAXParserFactory 實例(抽象工廠)
SAXParserFactory factory = SAXParserFactory.newInstance();
// 使用工廠創建 SAXParser(具體產品)
SAXParser parser = factory.newSAXParser();
// 使用 SAXParser 解析 XML 文檔
// 示例代碼忽略了具體 XML 文件的解析邏輯
System.out.println("SAXParser created successfully!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.這些應用解決了什么問題?
解耦客戶端與具體實現: 客戶端代碼不需要知道 DocumentBuilder 或 SAXParser 的具體實現類,只依賴于抽象工廠類和抽象產品類。這樣,XML 解析庫可以隨時切換實現(例如從 Apache Xerces 切換到其他實現),而無需修改客戶端代碼。
易于擴展: 當需要增加新的 DocumentBuilder 或 SAXParser 實現時,只需增加新的工廠類,而不需要修改客戶端。通過使用抽象工廠模式,可以很容易地擴展和適應新的需求。
集中控制對象創建: 使用工廠類來管理對象的創建,統一了對象的創建邏輯,避免了客戶端直接負責實例化的復雜性,使代碼更加靈活和可維護。
4.總結
在 JDK 中,諸如 DocumentBuilderFactory 和 SAXParserFactory 的類廣泛應用了抽象工廠模式。這種模式確保了客戶端代碼與具體實現的解耦,并增強了系統的可擴展性和靈活性。通過使用抽象工廠模式,JDK 能夠在不同的 XML 解析器之間自由切換,而不影響客戶端代碼。