當前位置: 妍妍網 > 碼農

最簡單的設計模式,抽象工廠模式,是否屬於過度設計?

2024-09-14碼農

在日常開發中,我們往往忽視了設計模式的重要性。 這可能是因為計畫時間緊迫,或者對設計模式理解不深。 其實,很多時候我們可能在不經意間已經使用了某些模式。

重要的是要有意識地學習和套用,讓程式碼更加優雅和高效。也許是時候重新審視我們的編程實踐,將設計模式融入其中了。

先從最簡單的設計模式【抽象工廠模式】走起,讓我們一起「重學設計模式」。

靈魂拷問 新增具體產品時,擴充套件新的工廠類再在客戶端中建立工廠類程式碼 和 在客戶端中直接建立例項程式碼,有什麽本質區別嗎?

一、不采用抽象工廠模式

假設你正在設計一套系統,該系統用於建立不同配置的電腦,例如PC和伺服器。每種型別的電腦都有不同的CPU和記憶體。

1、抽象產品

/**
 * 抽象產品 - CPU
 */

publicinterfaceCPU{
voidcompute();
}
/**
 * 抽象產品 - 記憶體
 */

publicinterfaceMemory{
voidstore();
}

2、具體產品

/**
 * 具體產品 - PC 的 CPU 實作
 */

public classPCCPUimplementsCPU{
@Override
publicvoidcompute(){
System.out.println("PC CPU is computing");
}
}
/**
 * 具體產品 - PC 的記憶體實作
 */

public classPCMemoryimplementsMemory{
@Override
publicvoidstore(){
System.out.println("PC Memory is storing data");
}
}
/**
 * 具體產品 - 伺服器的 CPU 實作
 */

public classServerCPUimplementsCPU{
@Override
publicvoidcompute(){
System.out.println("Server CPU is computing with high performance");
}
}
/**
 * 具體產品 - 伺服器的記憶體實作
 */

public classServerMemoryimplementsMemory{
@Override
publicvoidstore(){
System.out.println("Server Memory is storing data with high capacity");
}
}





3、測試類 ~ 不用抽象工廠模式

/**
 * 不用抽象工廠模式
 */

public classClient{
publicstaticvoidmain(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 classLaptopCPUimplementsCPU{
@Override
publicvoidcompute(){
System.out.println("Laptop CPU is computing efficiently");
}
}
/**
 * 具體產品 - 膝上型電腦的記憶體實作
 */

public classLaptopMemoryimplementsMemory{
@Override
publicvoidstore(){
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)是一種建立型設計模式,旨在提供一個介面,用於建立相關或依賴物件的家族,而無需指定它們的具體類。透過抽象工廠模式,客戶端可以透過介面建立一系列相關物件,而無需知道這些物件的具體實作。

抽象工廠模式通常包括以下幾個部份:

  1. 抽象工廠介面(Abstract Factory):定義建立產品的介面。

  2. 具體工廠類(Concrete Factory):實作抽象工廠介面,生成具體的產品。

  3. 抽象產品介面(Abstract Product):定義產品的介面。

  4. 具體產品類(Concrete Product):實作具體的產品。

  5. 客戶端(Client):透過工廠介面來使用產品,而不依賴於具體產品的實作。

1、抽象工廠介面

/**
 * 抽象工廠
 */

publicinterfaceComputerFactory{
CPU createCPU();
Memory createMemory();
}

2、具體工廠類

/**
 * 具體工廠 - PC 工廠
 */

public classPCFactoryimplementsComputerFactory{
@Override
public CPU createCPU(){
returnnew PCCPU();
}
@Override
public Memory createMemory(){
returnnew PCMemory();
}
}
/**
 * 具體工廠 - 伺服器工廠
 */

public classServerFactoryimplementsComputerFactory{
@Override
public CPU createCPU(){
returnnew ServerCPU();
}
@Override
public Memory createMemory(){
returnnew ServerMemory();
}
}



3、測試類 ~ 抽象工廠模式

public classAbstractFactoryClient{
publicstaticvoidmain(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 classLaptopFactoryimplementsComputerFactory{
@Override
public CPU createCPU(){
returnnew LaptopCPU();
}
@Override
public Memory createMemory(){
returnnew 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;
publicabstract classDocumentBuilderFactory{
// 用於建立 DocumentBuilder 例項
publicabstract DocumentBuilder newDocumentBuilder()throws ParserConfigurationException;
// 工廠方法,用於獲取 DocumentBuilderFactory 的例項
publicstatic 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 classDocumentBuilderFactoryImplextendsDocumentBuilderFactory{
@Override
public DocumentBuilder newDocumentBuilder()throws ParserConfigurationException {
returnnew DocumentBuilderImpl();
}
}

(3)客戶端使用

在客戶端程式碼中,使用 DocumentBuilderFactory 建立 DocumentBuilder 物件,而不需要關心具體實作。

透過 DocumentBuilderFactory.newInstance(),客戶端獲取了一個具體的 DocumentBuilderFactory 例項,然後使用該工廠建立 DocumentBuilder 物件。

客戶端不關心 DocumentBuilder 的具體實作,只依賴於抽象工廠介面,符合抽象工廠模式的設計思想。

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
public classXMLParserExample{
publicstaticvoidmain(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;
publicabstract classSAXParserFactory{
// 建立 SAXParser 的抽象方法
publicabstract SAXParser newSAXParser()throws ParserConfigurationException, SAXException;
// 獲取 SAXParserFactory 例項
publicstatic SAXParserFactory newInstance(){
return FactoryFinder.find(SAXParserFactory. class, "org.apache.xerces.jaxp.SAXParserFactoryImpl");
}
}

(2)具體工廠類:SAXParserFactoryImpl

SAXParserFactoryImpl 是 SAXParserFactory 的具體實作,負責建立 SAXParser。

package org.apache.xerces.jaxp;
public classSAXParserFactoryImplextendsSAXParserFactory{
@Override
public SAXParser newSAXParser()throws ParserConfigurationException, SAXException {
returnnew SAXParserImpl();
}
}

(3)客戶端使用

客戶端透過 SAXParserFactory 來建立 SAXParser,解析 XML 文件。

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
public classSAXParserExample{
publicstaticvoidmain(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 解析器之間自由切換,而不影響客戶端程式碼。


- EOF -

推薦閱讀點選標題可跳轉

·················END·················

看完本文有收獲?請轉發分享給更多人

關註「哪咤編程」,提升Java技能

點贊和在看就是最大的支持❤️