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


一、概述

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

二、解決問題

   通常我們需要一個對象的時候,會想到使用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、定義一個創建對象接口,由子類決定要實例化的類是哪一個;客戶端可以動態地指定工廠子類創建具體產品。

 

  

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM