設計模式-抽象工廠模式(AbstractFactory)-Java

設計模式-抽象工廠模式-Java


目錄




內容

  工廠方法模式通過引入工廠等級結構,解決了簡單工廠模式中工廠類類職責太重的問題,但由於工廠方法模式重的每個工廠只生產一類產品,可能會導致系統中存在大量的工廠類,勢必會增加系統的開銷。此時,我們可以考慮將一些相關的產品組成一個“產品族”,由一個工廠來同一生產。

1、需求-界面皮膚庫設計

  Sunny軟件公司欲開發一套界面皮膚庫,可以對Java桌面軟件進行界面美化。爲了保護版權,該皮膚庫源代碼不打算公開,而只向用戶提供已打包爲jar文件的Class字節碼文件。用戶在使用時可以通過菜單來選擇皮膚,不同的皮膚將提供視覺效果不同的按鈕、文本框、組合框等界面元素,其結構示意圖如圖1-1所示:
在這裏插入圖片描述
圖1界面皮膚庫結構示意圖

  該皮膚庫需要具備良好的靈活性和可擴展性,用戶可以資源選擇不同的皮膚,開發人員 可以在不修改既有代碼的基礎上增加新的皮膚。

  Sunny軟件公司的開發人員針對上述要求,決定使用工廠方法模式進行系統的設計,爲了保證系統的靈活性和可擴展性,提供一系列具體工廠來創建按鈕、文本框、組合框等界面元素,客戶端針對抽象工廠編程,初始結構如圖1-2所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3ZEUkZgj-1591442568161)(./images/skin_factoryMethod.png)]
圖1-2 基於工廠方法模式的界面皮膚庫初始結構圖

  在圖2中,提供了大量工廠來創建具體的界面組件,可以通過配置文件更換具體界面組件從而改變界面風格。但是,此設計方案存在如下問題:

  1. 當需要增加新的皮膚時,雖然不要修改現有代碼,但是需要增加大量類,針對每一個新增具體組件都需要增加一個具體工廠,類的個數成對增加,這無疑會導致系統越來越大,增加系統的維護成本和運行開銷;
  2. 由於同一種風格的具體界面組件通常要一起顯示,因此需要爲每個組件都選擇一個具體工廠類,用戶在使用時必須逐個進行設置,如果某個具體工廠選擇失誤將會導致界面顯示混亂,雖然我們可以適當增加一些約束語句,單客戶端代碼和配置文件都較爲複雜。

  如何減少系統中類的個數並保證客戶端每次始終只使用某一種風格的具體界面組件?這是Sunny公司開發人員所面臨的兩個問題,顯然,工廠方法模式無法解決這兩個問題,彆着急,本文所介紹的抽象工廠方法模式可以讓這些我呢提迎刃而解。

2、產品等級結構和產品族

  在工廠方法模式中具體工廠負責生產具體的產品,每一個具體工廠對應一種具體產品,工廠方法具有唯一性,一般情況下,一個具體工廠中之一一個或者一組重載的工廠方法。但是由時候我們希望一個工廠可以提供多個產品對象,而不是單一的產品對象,,如一個電器工廠,它可以生產電視機、電冰箱、空調等多種電器,而不是隻生產某一種電器。爲了更好地理解抽象工廠模式,我們先引入兩個概念:

  1. 產品等級結構:產品等級結構即產品的繼承結構,如一個抽象類是電視機,其子類由海爾電視機、海信電視機、TCL電視機,則抽象電視機與具體品牌的電視機之間構成了一個產品等級結構,抽象電視機是父類,而具體品牌的電視機是其子類。
  2. 產品族:在抽象工廠模式中,產品族是指由同一個工廠生產的,位於不同產品等級結構中的一組產品,如海爾電視機、海爾電冰箱,海爾電視機位於電視機產品等級結構中,,海爾電冰箱位於產品等級結構中,海爾電視機、海爾電冰箱構成了一個產品族。

  產品等級結構與產品族示意圖如圖2-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FgSMm8Wy-1591442568162)(./images/hierarchyAndFamily.png)]
圖2-1 產品族與產品等級結構示意圖

  在圖2-1中,不同顏色的多個正方形、圓形和橢圓形分別構成了三個產品等級結構,而相同顏色的正方形、圓形和橢圓形構成了一個產品族,每一個形狀對象都位於某個產品族,並屬於某個產品等級結構。圖2-1中一共有五個產品族,分屬於三個不同的產品等級結構。我們只要指明一個產品所處的產品族以及它所屬的等級結構,就可以位於確定這個產品。

 &emps;當系統所提供的工廠生產的具體產品並不是一個簡單的對象,而是多個位於不同產品等級結構、屬於不同了理想的具體產品時就可以使用抽象工廠模式。抽象工廠模式是所有形式的工廠模式中最爲抽象和最具有一般性的一種形式。抽象工廠模式與工廠方法模式最大的區別在於,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式需要面對多個產品等級結構,一個工廠等級結構可以負責多個不同產品等級結構中的產品對象的創建。當一個工廠等級結構可以創建除分屬於不同產品等級結構的一個產品族所有對象是,抽象工廠模式比工廠方法模式更爲簡單、更有效率。抽象工廠模式示意圖如圖2-2所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IbnTNvT2-1591442568164)(./images/ConcreteFactory.png)]
圖2-2 抽象工廠模式示意圖

  在圖2-2中,每一個具體工廠可以生產屬於一個產品族的所有產品,零利潤生產顏色相同的正方形、圓形和橢圓形,所生產的產品有位於不同的產品等級結構中。。如果使用工廠方法模式,圖2-2所示結構需要提供15個具體工廠,而使用抽象工廠模式只需要提供5個具體工廠,極大減少了系統中類的個數。

2、抽象工廠模式概述

  抽象工廠模式爲創建一種對象提供了一種解決方案。與工廠方法模式相比,抽象工廠模式中的具體工廠不只是創建一種產品,它負責一族產品。抽象工廠模式定義如下:

2.1、抽象工廠模式定義

  • 抽象工廠模式(Abstract Factory Pattern):提供一個創建一系列相關或相互依賴對象的接口,而無須指定他們具體的類。抽象工廠模式有稱爲Kit模式,他是一種對象創建型模式。

2.2、抽象工廠模式要點

  在抽象工廠模式中,每一個具體工廠都提供了多個工廠方法用於產生多種不同類型的產品,這些產品構成了一個產品族,抽象工廠模式結構如圖2.2-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IkQknsPe-1591442568166)(./images/model_abstractFactory.png)]
圖2.2-1 抽象工廠模式結構圖
  在抽象工廠模式結構圖中包含如下幾個角色:

2.3、抽象工廠模式結構圖中角色

  • Abstractory(抽象工廠):它聲明瞭一組用於創建一族產品的方法,每一個方法對應一種產品。
  • ConcreteFactory(具體工廠):它實現了在抽象工廠中聲明的創建產品的方法,生產一組具體產品,這些產品構成了一個產品族,每一個產品都位於某個產品等級結構中。
  • AbstractProduct(抽象產品):它爲每種產品聲明接口,在抽象產品中聲明瞭產品所具體的業務方法。
  • ConcreteProduct(具體產品):它定義了具體工廠生產的具體產品對象,實現抽象產品接口中聲明的業務方法。

2.4、抽象工廠模式使用

  在抽象工廠中聲明瞭多個工廠方法,用於創建不同類型的產品,抽象工廠可是是接口,也可以是抽象類或者具體類,其典型代碼如下所示:

abstract class AbstractFactory {
	public abstract AbstractProductA createProductA(); // 工廠方法1
	public abstract AbstractProductB createProductB(); // 工廠方法2
	...
}

  具體工廠實現了抽象工廠,每一個具體的工廠方法可是返回一個特定的產品對象,而同一個具體工廠所創建的產品對象構成了一個產品族。對於每一個具體工廠類,其典型代碼如下所示:

class ConcreteProduct1 extends AbstractFactory {
	// 工廠方法1
	public AbstractProductA createProductA() {
		return new ConcreteProductA1();
	}
	
	// 工廠方法2
	public AbstractProductB createProductB() {
		return new ConcreteProductB1();
	}
	
	...
}

  與個工廠方法模式一樣,抽象工廠模式也可以爲每一種產品提供一組重載的工廠方法,以不同方式對產品對象進行創建。

3、皮膚庫(抽象工廠模式)完整解決方案

  Sunny公司開發人員使用抽象工廠模式來重構界面皮膚庫的設計,其基本結構如圖3-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Qp0mYoPO-1591442568167)(./images/skin_abstractFactory.png)]
圖3-1 界面皮膚庫結構圖
  在圖3-1中,SkinFactory接口充當抽象工廠,其子類SpringSkinFactory和SummerSkinFactory充當具體工廠,接口Button、TextField和ComboBox充當抽象產品,其子類SpringButton、SummerButton、SpringTextField、SummerTextField、SpringComboBox、SummerComboBox充當具體產品。完整代碼如下所示:

// 在本例中我們對代碼進行了大量的簡化,實際使用是,界面組件的初始化代碼較爲複雜
  • Button接口代碼3-1:

      package abstractFactory.first;
    
      // Button接口
      public interface Button {
      	void display();
      }
    
  • SpringButton類代碼3-2:

      package abstractFactory.first;
    
      public class SpringButton implements Button{
    
      	@Override
      	public void display() {
      		System.out.println("淺綠色按鈕");
      	}
      }
    
  • SummerButton類代碼3-3:

      package abstractFactory.first;
    
      public class SummerButton implements Button{
    
      	@Override
      	public void display() {
      		System.out.println("淺藍色按鈕");
      	}
      }
    
  • TextField接口代碼3-4:抽象產品

      package abstractFactory.first;
    
      public interface TextField {
      	void display();
      }
    
  • SpringTextField類代碼3-5:具體產品

      package abstractFactory.first;
    
      public class SpringTextField implements TextField{
    
      	@Override
      	public void display() {
      		System.out.println("綠色文本框");
      	}	
      }
    
  • SummerTextField類代碼3-6:具體產品

      package abstractFactory.first;
    
      public class SummerTextField implements TextField{
    
      	@Override
      	public void display() {
      		System.out.println("藍色文本框");
      	}
      }
    
  • ComboBox接口代碼3-7:抽象產品

      package abstractFactory.first;
    
      public interface ComboBox {
      	void display();
      }
    
  • SpringComboBox類代碼3-8:具體產品

      package abstractFactory.first;
    
      public class SpringComboBox implements ComboBox{
    
      	@Override
      	public void display() {
      		System.out.println("綠色組合框");
      	}
      }
    
  • SummerComboBox類代碼3-9:具體產品

      package abstractFactory.first;
    
      public class SummerComboBox implements ComboBox{
    
      	@Override
      	public void display() {
      		System.out.println("藍色組合框");
      	}
      }
    
  • SkinFactory接口代碼3-10:抽象工廠

      package abstractFactory.first;
    
      // 皮膚抽象工廠
      public interface SkinFactory {
      	Button createButton();
      	TextField createTextField();
      	ComboBox createComboBox();
      }
    
  • SpringSkinFactory類代碼3-11:具體工廠

      package abstractFactory.first;
    
      public class SpringSkinFactory implements SkinFactory{
    
      	@Override
      	public Button createButton() {
      		return new SpringButton();
      	}
    
      	@Override
      	public TextField createTextField() {
      		return new SpringTextField();
      	}
    
      	@Override
      	public ComboBox createComboBox() {
      		return new SpringComboBox();
      	}
      }
    
  • SummerSkinFactory類代碼3-12:具體工廠

      package abstractFactory.first;
    
      public class SummerSkinFactory implements SkinFactory{
    
      	@Override
      	public Button createButton() {
      		return new SummerButton();
      	}
    
      	@Override
      	public TextField createTextField() {
      		return new SummerTextField();
      	}
    
      	@Override
      	public ComboBox createComboBox() {
      		return new SummerComboBox();
      	}
      }
    
  • Utils工具類代碼3-13:讀取配置文件中具體工廠類名,返回具體工廠類對象

      package abstractFactory.first;
    
      import java.util.Properties;
    
      public class Utils {
      	public static SkinFactory getSkinFactory() {
      		try {
      			SkinFactory factory = null;
      			Properties prop = new Properties();
      			prop.load(Utils.class.getClassLoader().getResourceAsStream("skin.properties"));
      			String className = prop.getProperty("className");
      			factory = (SkinFactory)(Class.forName(className).newInstance());
      			return factory;
      		}catch (Exception e) {
      			e.printStackTrace();
      			return null;
      		}
      	}
      }
    
  • skin.properties配置文件:配置具體工廠類類名

      className=abstractFactory.first.SpringSkinFactory
    
  • Client類代碼3-14:客戶端測試類

      package abstractFactory.first;
    
      public class Client {
      	public static void main(String[] args) {
      		SkinFactory factory = Utils.getSkinFactory();
      		Button btn = factory.createButton();
      		TextField tf = factory.createTextField();
      		ComboBox cb = factory.createComboBox();
      		btn.display();
      		tf.display();
      		cb.display();
      	}
      }
    
  • 測試結構:

      淺綠色按鈕
      綠色文本框
      綠色組合框
    

 &emsp

4、案例總結

  如果需要更換皮膚,只需修改配置文件即可,在實際環境中,我們可以提供可視化界面,如菜單或者窗口來修改配置文件,用戶無須之間修改配置文件。如果需要增加新的皮膚,只需增加一族新的具體組件並對應提供一個新的具體工廠,修改配置文件即可使用新的皮膚,由於代碼無須修改,符合“開閉原則”。

4.1、擴展

  在真實項目開發中,我們通常會爲配置文件提供一個可視化的編輯界面,類上Structs框架中的structs.xml編輯器,大家可以自行開發一個簡單的圖形化工具來修改配置文件,實現真正的純界面操作。

4.2、開閉原則的傾斜性

  Sunny公司使用抽象工廠模式設計了界面皮膚庫,該皮膚庫可以較爲方便地增加新的皮膚,但是現在遇到一個非常嚴重的問題:由於設計時考慮不全面,忘記爲單選按鈕(RadioButton)提供
不同皮膚的風格化顯示,導致無論選擇哪種皮膚,單選按鈕都顯得那麼“格格不入”。Sunny公司的設計人員決定向系統中增加單選按鈕,但是發現原有系統居然不能夠在符合“開閉原則”的前提下增加新的組件,原因是抽象工廠SkinFactory中根本沒有提供創建單選按鈕的方法,如果需要增加單選按鈕,首先需要修改抽象工廠接口SkinFactory,在其中新增聲明創建單選按鈕的方法,然後逐個修改具體工廠類,增加相應方法以實現在不同的皮膚中創建單選按鈕,此外
還需要修改客戶端,否則單選按鈕無法應用於現有系統。

  怎麼辦?答案是抽象工廠模式無法解決該問題,這也是抽象工廠模式最大的缺點。在抽象工廠模式中,增加新的產品族很方便,但是增加新的產品等級結構很麻煩,抽象工廠模式的這種性質稱爲“開閉原則”的傾斜性。“開閉原則”要求系統對擴展開放,對修改封閉,通過擴展達到增強其功能的目的,對於涉及到多個產品族與多個產品等級結構的系統,其功能增強包括
兩方面:

  • (1) 增加產品族:對於增加新的產品族,抽象工廠模式很好地支持了“開閉原則”,只需要增加具體產品並對應增加一個新的具體工廠,對已有代碼無須做任何修改。
  • (2) 增加新的產品等級結構:對於增加新的產品等級結構,需要修改所有的工廠角色,包括抽象工廠類,在所有的工廠類中都需要增加生產新產品的方法,違背了“開閉原則”。

  正因爲抽象工廠模式存在“開閉原則”的傾斜性,它以一種傾斜的方式來滿足“開閉原則”,爲增加新產品族提供方便,但不能爲增加新產品結構提供這樣的方便,因此要求設計人員在設計之初就能夠全面考慮,不會在設計完成之後向系統中增加新的產品等級結構,也不會刪除已有的產品等級結構,否則將會導致系統出現較大的修改,爲後續維護工作帶來諸多麻煩。

5、抽象工廠模式總結

  抽象工廠模式是工廠方法模式的進一步延伸,由於它提供了功能更爲強大的工廠類並且具備較好的可擴展性,在軟件開發中得以廣泛應用,尤其是在一些框架和API類庫的設計中,例如在Java語言的AWT(抽象窗口工具包)中就使用了抽象工廠模式,它使用抽象工廠模式來實現在不同的操作系統中應用程序呈現與所在操作系統一致的外觀界面。抽象工廠模式也是在軟件開發中最常用的設計模式之一。

5.1、優缺點

  • 主要優點:

    • 抽象工廠模式隔離了具體類的生成,使得客戶並不需要知道什麼被創建。由於這種隔離,更換一個具體工廠就變得相對容易,所有的具體工廠都實現了抽象工廠中定義的那些公共接口,因此只需改變具體工廠的實例,就可以在某種程度上改變整個軟件系統的行爲。
    • 當一個產品族中的多個對象被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的對象。
    • 對於增加新的產品族,抽象工廠模式很好地支持了“開閉原則”,只需要增加具體產品並對應增加一個新的具體工廠,對已有代碼無須做任何修改。
  • 主要缺點:

    • 增加新的產品等級結構麻煩,需要對原有系統進行較大的修改,甚至需要修改抽象層代碼,這顯然會帶來較大的不便,違背了“開閉原則”。

5.2、適用場景

  在以下情況下可以考慮使用抽象工廠模式:

  • (1) 一個系統不應當依賴於產品類實例如何被創建、組合和表達的細節,這對於所有類型的工廠模式都是很重要的,用戶無須關心對象的創建過程,將對象的創建和使用解耦。
  • (2) 系統中有多於一個的產品族,而每次只使用其中某一產品族。可以通過配置文件等方式來使得用戶可以動態改變產品族,也可以很方便地增加新的產品族。
  • (3) 屬於同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。同一個產品族中的產品可以是沒有任何關係的對象,但是它們都具有一些共同的約束,如同一操作系統下的按鈕和文本框,按鈕與文本框之間沒有直接關係,但它們都是屬於某一操作系統的,此時具有一個共同的約束條件:操作系統的類型。
  • (4) 產品等級結構穩定,設計完成之後,不會向系統中增加新的產品等級結構或者刪除已有的產品等級結構。

後記

  參考文獻:Java設計模式(劉偉).pdf。持續更新,歡迎交流,本人QQ:806797785

前端項目源代碼地址:https://gitee.com/gaogzhen/vue-leyou
後端JAVA源代碼地址:https://gitee.com/gaogzhen/JAVA
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章