設計模式(四):工廠方法模式(解析設計原則)

来源:http://www.cnblogs.com/jenkinschan/archive/2016/07/27/5693358.html
-Advertisement-
Play Games

一、概述 工廠方法模式定義了一個創建對象的介面,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。 二、解決問題 通常我們需要一個對象的時候,會想到使用new來創建對象 Tea tea = new MilkTea(); //使用了介面,代碼更有彈性,體現設計原則“對介面編程,而不是對 ...


一、概述

  工廠方法模式定義了一個創建對象的介面,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。

二、解決問題

   通常我們需要一個對象的時候,會想到使用new來創建對象

      Tea tea = new MilkTea(); //使用了介面,代碼更有彈性,體現設計原則“對介面編程,而不是對實現編程”

  當我們需要多個對象的時候,”對介面編程“的原則似乎還能派上用場   

 

Tea tea;
if("milk".equals(type)){
	tea = new MilkTea();
}else if("coffee".equals(type)){
	tea = new CoffeeTea();
}else if("lemon".equals(type)){
	tea = new LemonTea();
}

   這裡也體現了“對介面編程”的好處,運行時決定要實例化哪個對象,讓系統具備了彈性,但對於擴展性方面,我們就不敢恭維了。看以上代碼,當我們要新增對象或者要擴展時,不得不打開這份代碼進行檢查和修改。通常這樣的修改過的代碼會造成部分系統更難維護和更新,而且也容易犯錯。

  為了有良好的擴展性,我們想到了另外一個設計原則把變化的代碼從不變化的代碼中分離出來

  假設我們要開一家燒餅店,我們每天會做各種口味的燒餅出售,做燒餅的程式包括準備原材料、和麵、烘烤、切片、裝盒。

  

//燒餅店
public class ShaobingStore {
        public Shaobing orderShaobing(String type){
                Shaobing shaobing = null;
                if("onion".equals(type)){
                         //洋蔥燒餅
                         shaobing = new OnionShaobing();
                }else if("sour".equals(type)){
                         //酸菜燒餅
                         shaobing = new SourShaobing();
                }else if("beef".equals(type)){
                         //牛肉燒餅
                         shaobing = new BeefShaobing();
                }
                //以上代碼會發生改變,當洋蔥燒餅不再出售時,我們會把創建洋蔥的代碼刪除,我們可能會新增新口味的燒餅  
                 else if("pork".equals(type)){
                         //牛肉燒餅
                         shaobing = new PorkShaobing();
                }       
                //對於製作燒餅的程式中,以下這些步驟是不變的
                if(shaobing != null){
                        shaobing.prepare();
            shaobing.cut();
            shaobing.bake();  
                        shaobing.box();
                }
                return shaobing;
        }                      
}

  對於上面代碼,我們使用”分離變化“的原則,把創建燒餅代碼封裝到一個類中,我們把它叫做工廠類,裡面專門一個方法用來創建燒餅,如下所示:

package factorymethod.pattern;

public class SimpleShaobingFactory {
	public Shaobing createShaobing(String type){
		Shaobing shaobing = null;
		if("onion".equals(type)){
			//洋蔥燒餅
			shaobing = new OnionShaobing();
		}else if("sour".equals(type)){
			//酸菜燒餅
			shaobing = new SourShaobing();
		}else if("beef".equals(type)){
			//牛肉燒餅
			shaobing = new BeefShaobing();
		}

		return shaobing;
	}
}

  改進後的燒餅店如下:

package factorymethod.pattern;

//燒餅店
public class ShaobingStore {

  public Shaobing orderShaobing(String type){
	  Shaobing shaobing = null;
	  
	  shaobing = factory.createShaobing(type);

	  //對於製作燒餅的程式中,以下這些步驟是不變的
	  if(shaobing != null){   	 
		  shaobing.prepare();
		  shaobing.cut();
		  shaobing.bake();
		  shaobing.box();
	   }
	     
	   return shaobing;
  }  

  SimpleShaobingFactory factory;

  public ShaobingStore(SimpleShaobingFactory factory){
     this.factory = factory;
  }

}

如上圖所示,不管以後燒餅的口味怎麼變,燒餅店的代碼都不用變了,要擴展或者修改燒餅,我們只要更改創建燒餅的工廠類,也就是SimpleShaobingFactory 類,這就解開了燒餅店和燒餅的耦合,體現了對擴展開放,對修改關閉的設計原則。

  其實上面的改進方案使用了一個沒有被真正冠名的設計模式簡單工廠模式,其類圖如下所示:

 

 從上面的類圖來看,如果我們的燒餅店開在不同的地方,不同地方對洋蔥燒餅,酸菜燒餅要求的口味不一樣,北方人喜歡放辣椒,南方人喜歡清淡的,我們的燒餅店該怎麼開呢?這就是工廠方法模式要幫我們解決的問題,工廠方法模式讓類把實例化推遲到子類,讓子類決定實例化的類是哪一個,將產品的“實現”從“使用”中解耦出來,讓系統同時具備了彈性和擴展性。簡單工廠不夠彈性,不能改變正在創建的產品(同一種類型的只有一個,拿洋蔥燒餅來說,全國各地的口味一樣,沒有辣與不辣的區分了)

三、結構類圖

     

四、成員角色

   抽象創建者(Creator):定義了創建對象模板,實現了所有操縱產品的方法,除了工廠方法。具體創建者必須繼承該類,實現工廠方法。

  具體創建者(ConcreteCreator):繼承抽象創建者,實現工廠方法,負責創建產品對象。

  抽象產品(Product):定義了產品的共用資源,提供給子類繼承使用,某些方法可以做成抽象方法,強制子類實現。

  具體產品(ConcreteProduct):繼承自抽象產品,實現父類的抽象方法,也可以覆蓋父類的方法,從而產生各種各類的產品。

五、應用實例  

  下麵還是以開燒餅店為例,介紹如何在廣州和長沙開燒餅店,賣適合當地風味的燒餅,而且燒餅的種類和名稱一樣。

  首先抽象燒餅店,也就是Creator

package factorymethod.pattern;

public abstract class ShaobingStore {
	public Shaobing orderShaobing(String type){
		Shaobing shaobing = createShaobing(type);
		
		shaobing.prepare();
		shaobing.cut();
		shaobing.bake();
		shaobing.box();
		
		return shaobing;
	}
	
	//未實現的工廠方法
	public abstract Shaobing createShaobing(String type);
		
}

  第二步,創建抽象燒餅,也就是Product

package factorymethod.pattern;

public abstract class Shaobing {
	//燒餅名稱
	public String name;
	//燒餅用的配料
	public String sauce;
	//麵團
	public String dough;
	
	public void prepare(){
		System.out.println("Prepareing " + name);
		//和麵
		System.out.println("Kneading dough...");
		//加配料
		System.out.println("加配料:" + sauce);
	}
	
	//烤燒餅
	public void bake(){
		System.out.println("Bake for 25 minutes at 350C");
	}
	
	//切麵團
	public void cut(){
		System.out.println("Cutting the dough into fit slices");
	}
	
	//打包
	public void box(){
		System.out.println("Place shaobing into official box");
	}
}

  第三步、創建廣州風味的燒餅(加番茄醬的洋蔥燒餅和牛肉燒餅),對應ConcreteProduct

package factorymethod.pattern;

public class GZOnionShaobing extends Shaobing{
	public GZOnionShaobing(){
		name = "廣州的洋蔥燒餅";
		//配料
		sauce = "番茄醬";
	}
}

  

package factorymethod.pattern;

public class GZBeefShaobing extends Shaobing{
	public GZBeefShaobing(){
		name = "廣州的牛肉燒餅";
		//配料
		sauce = "番茄醬";
	}
}

  第四步、創建長沙風味的燒餅(加辣椒醬的洋蔥燒餅和牛肉燒餅),對應ConcreteProduct

package factorymethod.pattern;

public class CSOnionShaobing extends Shaobing{
	public CSOnionShaobing(){
		name = "長沙洋蔥燒餅";
		//配料
		sauce = "辣椒醬";
	}
}

  

package factorymethod.pattern;

public class CSBeefShaobing extends Shaobing{
	public CSBeefShaobing(){
		name = "長沙牛肉燒餅";
		//配料
		sauce = "辣椒醬 ";
	}
}

  第五步、創建廣州燒餅店,對應ConcreteCreator

package factorymethod.pattern;

//廣州燒餅店
public class GZShaobingStore extends ShaobingStore{

	@Override
	public Shaobing createShaobing(String type) {
		Shaobing shaobing = null;
		if("onion".equals(type)){
			shaobing = new GZOnionShaobing();
		}else if("beef".equals(type)){
			shaobing = new GZBeefShaobing();
		}
		
		return shaobing;
	}
}

  第六步、創建長沙燒餅店,對應ConcreteCreator

package factorymethod.pattern;

//長沙燒餅店
public class CSShaobingStore extends ShaobingStore{

	@Override
	public Shaobing createShaobing(String type) {
		Shaobing shaobing = null;
		if("onion".equals(type)){
			shaobing = new CSOnionShaobing();
		}else if("beef".equals(type)){
			shaobing = new CSBeefShaobing();
		}
		
		return shaobing;
	}

}

  第七步、測試售出名字相同但風味不一樣的燒餅

package factorymethod.pattern;

public class TestShaobingStore {
	public static void main(String[] args){
		//在廣州開一個燒餅店
		ShaobingStore gzStore = new GZShaobingStore();
		//售出一個洋蔥燒餅
		gzStore.orderShaobing("onion");
		System.out.println("----------------------");
		//在長沙開一個燒餅店
		ShaobingStore csStore = new CSShaobingStore();
		//售出一個洋蔥燒餅
		csStore.orderShaobing("onion");
	}
}

  運行結果:

六、工廠方法特有的設計原則

  如果我們之間在燒餅店中直接實例化一個燒餅,這種設計師依賴具體類的,類圖如下:

  

這種依賴具體類設計,擴展性、彈性、維護性都比較差。如果將實例化的代碼獨立出來,使用工廠方法,我們將不再依賴具體類了,請看如下類圖:

這就是我們要講的依賴倒置原則:要依賴抽象,不要依賴具體類。用依賴倒置原則設計的系統,使得對象的實現從使用中解耦,對象的使用是在Creator,實現卻在ConcreteCreator中,Creator只有Product的引用,Creator與ConcreteProduct松耦合,這種設計很強的擴展性、彈性和可維護性。

  設計中使用以來倒置原則方法:

  1、變數不可以持有具體類的引用(就是不能使用new,使用工廠方法)

  2、不要讓類派生自具體類(繼承抽象或者實現介面)

  3、不要覆蓋基類中已實現的方法

 

七、優點和缺點

  1、優點

  (1)、符合“開閉”原則,具有很強的的擴展性、彈性和可維護性。擴展時只要添加一個ConcreteCreator,而無須修改原有的ConcreteCreator,因此維護性也好。解決了簡單工廠對修改開放的問題。

  (2)、使用了依賴倒置原則,依賴抽象而不是具體,使用(客戶)和實現(具體類)松耦合。

  (3)、客戶只需要知道所需產品的具體工廠,而無須知道具體工廠的創建產品的過程,甚至不需要知道具體產品的類名。

  2、缺點

  (1)、一個具體產品對應一個類,當具體產品過多時會使系統類的數目過多,增加系統複雜度。

  (1)、每增加一個產品時,都需要一個具體類和一個具體創建者,使得類的個數成倍增加,導致系統類數目過多,複雜性增加。

  (2)、對簡單工廠,增加功能修改的是工廠類;對工廠方法,增加功能修改的是客戶端。

八、使用場合

  1、當需要一個對象時,我們不需要知道該對象所對應的具體類,只要知道哪個具體工廠可以生成該對象,實例化這個具體工廠即可創建該對象。

  2、類的數目不固定,隨時有新的子類增加進來,或者是還不知道將來需要實例化哪些具體類。

  3、定義一個創建對象介面,由子類決定要實例化的類是哪一個;客戶端可以動態地指定工廠子類創建具體產品。

 

  

  

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.錯誤: 在eclipse中使用run->run on server的時候,選擇tomcat6會報錯誤:The server does not support version 3.0 of the J2EE Web module specification 如下所示: 2.原因: Tomcat 6 ...
  • R語言中的機器學習程式包主要如下所示: ...
  • learn python3   這是我初學Python時寫的一套Python基礎示常式序.主要基於廖雪峰老師的Python3教程和<<深入理解Python>>. 感謝! 下麵是這些示常式序的目錄總結:  Chapter1:容器/集合/C ...
  • @(MyBatis)[Cache] MyBatis源碼分析——Cache構建以及應用 SqlSession使用緩存流程 如果開啟了二級緩存,而Executor會使用CachingExecutor來裝飾,添加緩存功能,該CachingExecutor會從MappedStatement中獲取對應的Cac ...
  • 當我們享受著jdk帶來的便利時同樣承受它帶來的不幸惡果。通過分析Hashtable就知道,synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨占,安全的背後是巨大的浪費,而現在的解決方案 ConcurrentHashMap。 ConcurrentHashMap和Hashtable ...
  • 正好需要,在網上找了好久,記錄一下 ...
  • 一、概念 1、定義 反應堆模式是一種對象行為類的設計模式,對同步事件分揀和派發。它是處理併發I/O比較常見的一種模式,用於同步I/O。 其中心思想是將所有要處理的I/O事件註冊到一個中心I/O多路復用器上,同時主線程阻塞在多路復用器上;一旦有I/O事件到來或者是準備就緒,多路復用器返回並將相應的I/ ...
  • 畢業到轉行以來有一年時間了,成為一名程式猿也有大半年了,之前在新浪上隨便寫寫簡單的學習過程,感覺不夠像那麼回事,現在接觸前端也有一段時間了,也做過幾個項目,認識到可以拓展的實在太多了,希望從這裡起步,踏踏實實,記錄好點點滴滴。 HHL Gulp使用步驟: 1 安裝node(npm),全局安裝,我使用 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...