4.Factory Pattern(工廠模式)

来源:http://www.cnblogs.com/lanshanxiao/archive/2017/12/07/7999847.html
-Advertisement-
Play Games

工廠模式(Factory Pattern)定義: 定義了一個創建對象的介面,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。 針對實現編程,但是當我們每次使用new時候,不正是在針對實現編程嗎?是的,當使用“new”時,你的確實在實例化一個具體類,所以用的確實是實現,而不是介面。代 ...


工廠模式(Factory Pattern)定義:

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

  針對實現編程,但是當我們每次使用new時候,不正是在針對實現編程嗎?是的,當使用“new”時,你的確實在實例化一個具體類,所以用的確實是實現,而不是介面。代碼綁定這具體類會導致代碼更脆弱,更缺乏彈性。

  Duck duck                          =                           new GreenDuck();

  使用介面讓代碼具有彈性                                 但還是得建立具體類的實例

  但我們總是要用new來創建對象吧。是的,在技術上,new沒有錯,畢竟這是Java基礎部分。真正的犯人是我們的老朋友 “改變”,以及它是如何影響new的使用的。

看下麵的代碼:

 1 /*當有一群相關的具體類時,通常會寫出這樣的代碼*/
 2 Duck duck;
 3 
 4 if(picnic){
 5     duck = new GreenDuck();
 6 }else if(hunting){
 7     duck = new DecoyDuck();
 8 }else if(inBathTub){
 9     duck = new RubberDuck();
10 }...//還有更多的實現類,在運行程式時,要通過判斷,來確定創建哪個對象
View Code

  針對介面編程,可以隔離掉以後系統可能發生的一大堆改變。如果代碼是針對介面編程而寫,那麼通過多態,它可以與任何新類實現該介面。但是,當代碼使用大量的具體類時,等於是自找麻煩,因為一旦加入新的具體類,就必須改變代碼。也就是說,你的代碼並非 “對修改關閉”。想用新的具體類型來擴展代碼,必須重新打開它。所以,遇到問題時候,就應該回到OO設計原則去尋找線索。別忘了,我們的第一個原則用來處理改變,並幫助我們 “找出會變化的方面,把它們從不變的部分分離出來”

舉例:

  有一家比薩店,定製比薩的方法如下:

 1 Pizza orderPizza(String type){//這隻是類中一個方法,將想要定製的比薩的type類型傳入,就會製作出相應的比薩
 2     Pizza pizza;
 3     
 4     if(type.equals("cheese")){//根據比薩的type類型,實例化正確的具體類,然後將其賦值給pizza實例變數。
 5         pizza = new CheesePizza();//請註意,這裡的任何比薩都必須實現Pizza介面
 6     }else if(type.equals("greek"){
 7         pizza = new GreekPizza();
 8     }else if(type.equals("pepperoni"){
 9         pizza = new PepperoniPizza();
10     }
11     
12     
13     pizza.prepare();//一旦知道要製作什麼類型的比薩,那麼我們將要進行加工
14     pizza.bake();//這四個方法,分別是製作比薩的四個步驟
15     pizza.cut();
16     pizza.box();
17     
18     return pizza;
19 }
orderPizza

 正如上面所說,我們要製作新的比薩具體類或者刪除現有的比薩具體類,都需要打開orderPizza()方法所在的類,來添加或刪除裡面的實例化。

我們要做的是,找出會變化的方面,把它們從不變的部分分離出來,現在我們將判斷製作比薩類型部分從orderPizza()方法中分離出來:

 1 Pizza orderPizza(String type){//這隻是類中一個方法,將想要定製的比薩的type類型傳入,就會製作出相應的比薩
 2     Pizza pizza;
 3     
 4     //原先實例化比薩的部分被分離出去
 5     
 6     pizza.prepare();//一旦知道要製作什麼類型的比薩,那麼我們將要進行加工
 7     pizza.bake();//這四個方法,分別是製作比薩的四個步驟
 8     pizza.cut();
 9     pizza.box();
10     
11     return pizza;
12 }
orderPizza
 1 public class SimplePizzaFactory{
 2     public Pizza createPizza(String type){
 3         Pizza pizza = null;
 4         
 5         if(type.equals("cheese")){//根據比薩的type類型,實例化正確的具體類,然後將其賦值給pizza實例變數。
 6             pizza = new CheesePizza();//請註意,這裡的任何比薩都必須實現Pizza介面
 7         }else if(type.equals("pepperoni"){//刪除了一些比薩,又增加了一些比薩
 8             pizza = new PepperoniPizza();
 9         }else if(type.equals("clam"){
10             pizza = new ClamPizza();
11         }else if(type.equals("veggie"){
12             pizza = new VeggiePizza();
13         }
14         
15         return pizza;
16     }
17 }
SimplePizzaFactory

  從上面代碼可以看出,我們將orderPizza()方法中實例化比薩的部分分離出來,單獨放入了SimplePizzaFactory類中。

  那麼,關於new的問題還是沒有解決,我們還是針對實現編程,只是把針對實現編程的部分搬到了另一個對象中。問題還是存在,沒錯,確實存在。但是我們已經做到最小化改變代碼(以我們現在所學的知識,還不足以脫離new來實例化對象,所以肯定要針對實現編程。若不能避免,那就做到最好!),若以後要修改創建比薩實例部分,就只要打開SimplePizzaFactory類就可以修改。

  現在我們將PizzaStore(比薩店)類實現:

 1 public class PizzaStore{
 2     SimplePizzaFactory factory;//比薩工廠對象
 3     
 4     public PizzaStore(SimplePizzaFactory factory){//在構造器中,需要一個工廠對象作為參數
 5         this.factory = factory;
 6     }
 7     
 8     public Pizza orderPizza(String type){
 9         Pizza pizza;
10         
11         pizza = factory.createPizza(type);//將實例化比薩對象的責任交給工廠對象
12         
13         pizza.prepare();//一旦知道要製作什麼類型的比薩,那麼我們將要進行加工
14         pizza.bake();//這四個方法,分別是製作比薩的四個步驟
15         pizza.cut();
16         pizza.box();
17         
18         return pizza;
19     }
20 }
PizzaStore

上面的就是簡單工廠模式,這隻是小本生意的開始。我們只開了一家比薩店,現在我們的比薩店出名了,要開跨地區連鎖店,規定每個地區開一家。我們做的比薩要符合當地人的口味。於是各個地區的比薩口味又不一樣。

  不多說先把Pizza抽象類實現了:

 1 import java.util.ArrayList;
 2 
 3 public abstract class Pizza{
 4     String name;//比薩名字
 5     String dough;//麵團
 6     String sauce;//
 7     ArrayList toppings = new ArrayList();//配料列表
 8     
 9     void prepare(){//準備做比薩方法
10         System.out.println("Preparing " + name);
11         System.out.println("Tossing dough...");
12         System.out.println("Adding sauce...");
13         System.out.println("Adding toppings: ");
14         
15         for(int i = 0; i < toppings.size(); i++){
16             System.out.println("   " + toppings.get(i));
17         }
18     }
19     
20     void bake(){//烘焙方法
21         System.out.println("Bake for 25 minutes at 350");
22     }
23     
24     void cut(){//切塊方法
25         System.out.println("Cutting the pizza into diagonal slices");
26     }
27     
28     void box(){//裝盒方法
29         System.out.println("Place pizza in official PizzaStore box");
30     }
31     
32     public String getName(){//獲取名稱方法
33         return name;
34     }
35 }
Pizza

  然後根據各地區做出不同的Pizza:

1 public class NYStyleCheesePizza extends Pizza{//NewYork比薩
2     public NYStyleCheesePizza(){
3         name = "NY Style Sauce and Cheese Pizza";
4         dough = "Thin Crust Dough";
5         sauce = "Marinara Sauce";
6         
7         toppings.add("Grated Reggiano Cheese");
8     }
9 }
NYStyleCheesePizza
 1 public class ChicagoStyleCheesePizza extends Pizza{//Chicago比薩
 2     public ChicagoStyleCheesePizza(){
 3         name = "Chicago Style Deep Dish Cheese Pizza";
 4         dough = "Extra Thick Crust Dough";
 5         sauce = "Plum Tomato Sauce";
 6         
 7         toppings.add("Shredded Mozzarella Cheese");
 8     }
 9     
10     void cut(){
11         System.out.println("Cutting the pizza into square slices");
12     }
13 }
ChicagoStyleCheesePizza

  實現PizzaStore抽象類:

 1 public abstract class PizzaStore{//所有地區的PizzaStore都要繼承該抽象類
 2     public Pizza orderPizza(String type){//定製Pizza方法
 3         Pizza pizza;
 4         
 5         pizza = createPizza(type);//創建Pizza實例
 6         
 7         pizza.prepare();
 8         pizza.bake();
 9         pizza.cut();
10         pizza.box();
11         
12         return pizza;
13     }
14     
15     abstract Pizza createPizza(String type);//創建Pizza實例的方法由每個地區的PizzaStore子類來實現
16 }
PizzaStore

  每個地區開一個PizzaStore:

1 public class NYPizzaStore extends PizzaStore{//NewYork店
2     Pizza createPizza(String item){
3         if(item.equals("cheese")){//可以製作出cheese口味的Pizza
4             return new NYStyleCheesePizza();
5         }else return null;//也可以製作出其他口味的Pizza,我就不實現了。
6     }
7 }
NYPizzaStore
1 public class ChicagoPizzaStore extends PizzaStore{//Chicago店
2     Pizza createPizza(String item){
3         if(item.equals("cheese")){//可以製作出Cheese口味的Pizza
4             return new ChicagoStyleCheesePizza();
5         }else return null;//也可以製作出其他口味的Pizza,我就不實現了。
6     }
7 }
ChicagoPizzaStore

  測試類:

 1 public class PizzaTestDrive{
 2     
 3     public static void main(String args[]){
 4         //先開店
 5         PizzaStore nyStore = new NYPizzaStore();
 6         PizzaStore chicagoStore = new ChicagoPizzaStore();
 7         
 8         //NYPizzaStore店製作出cheese口味的Pizza
 9         Pizza pizza = nyStore.orderPizza("cheese");
10         System.out.println("Ethan ordered a " + pizza.getName() + "\n");
11         //ChicagoPizzaStore店製作出cheese口味的Pizza
12         pizza = chicagoStore.orderPizza("cheese");
13         System.out.println("Joel ordered a " + pizza.getName() + "\n");
14     }
15 }
PizzaTestDrive

編譯運行結果:

   上面的就是工廠模式。

總結一下,有兩個抽象類:

1.Pizza抽象類:這個抽象類是產品抽象類,換句話說就是工廠要製作出的產品。所有的Pizza子類都要繼承自該Pizza抽象類。

2.PizzaStore抽象類:這個抽象類是工廠抽象類,這個抽象類中有一個createPizza()抽象方法,這個方法就是為了讓子類去實現自己的產品類。這樣一來,子類可以根據自己的情況,隨心所欲的生產符合自己口味的產品。

再細細地品味一下工廠模式的定義:

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

由工廠子類決定要實例化哪一個類,不要理解錯誤。並不是指模式允許子類本身在運行時做決定,而是指在編寫工廠抽象類時,不需要知道實際創建的產品是哪一個。選擇使用了哪個子類,自然就決定了實際創建的產品是什麼。

這裡溫馨提醒一下:在設計模式中,所謂的 “實現一個介面” 並 “不一定” 表示 “寫一個介面,並利用implements關鍵字來實現這個介面”。“實現一個介面” 泛指 “實現某個超類型(可以是類或介面)的某個方法” 。


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

-Advertisement-
Play Games
更多相關文章
  • 推薦方法一: <meta name="referrer" content="never"> 就這麼一句話,把它放到<head>頭裡就可以了。記得要把其他影響圖片的東西給刪了,不然會衝突。 我自己本人的,因為我希望點擊一個鏈接時跳轉到微信圖文,然後在該頁面點擊返回圖標從微信圖文返回之前的APP頁面,我 ...
  • 你遇到過性能很差的網頁嗎? 這種網頁響應非常緩慢,占用大量的CPU和記憶體,瀏覽起來常常有卡頓,頁面的動畫效果也不流暢。 你會有什麼反應?我猜想,大多數用戶會關閉這個頁面,改為訪問其他網站。作為一個開發者,肯定不願意看到這種情況,那麼怎樣才能提高性能呢? 本文將詳細介紹性能問題的出現原因,以及解決方法 ...
  • 第一種是比較常規的方法 思路: 1.構建一個新的數組存放結果 2.for迴圈中每次從原數組中取出一個元素,用這個元素迴圈與結果數組對比 3.若結果數組中沒有該元素,則存到結果數組中 代碼如下: Array.prototype.unique1 = function(){ var res = [this ...
  • 1、const聲明一個只讀的常量。一旦聲明,常量的值就不能改變。 2、const的作用域與let命令相同:只在聲明所在的塊級作用域內有效。 3、const命令聲明的常量也是不提升,只能在聲明的位置後面使用。 4、ES6中:var命令和function命令聲明的全局變數,依舊是頂層對象的屬性;另一方面 ...
  • 阿子原創,轉載請註明出處。 在使用jQuery Mobile Slider時,發現在頁面上下拖動時,很容易不小心觸發Slider的點擊事件,從而造成誤操作。為此需要禁用Slider的點擊事件。 官方API並沒有提供禁用點擊事件的事件的方法,因此要實現這一功能只能另闢蹊徑。 留意到點擊Slider時都 ...
  • 1、實現塊作用域 2、不存在變數提升。 ES6 明確規定,如果區塊中存在let和const命令,這個區塊對這些命令聲明的變數,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變數,就會報錯。 ...
  • 網頁性能優化 頁面優化 頁面優化可以提升頁面的訪問速度從而提高用戶體驗,優化的頁面可以更好的提升 SEO 的效果同時也可以提高代碼的可讀性和維護性。 網頁的生成過程,大致可以分成五步。 1. HTML代碼轉化成DOM 2. CSS代碼轉化成CSSOM(CSS Object Model) 3. 結合D ...
  • 超級偶數(SuperEven)是指每一位都是偶數的正整數,例如: 0,2,4,6,8,20,22,24,26,28,40,...,88,200,202,... 要求寫一個函數,輸入項數n,返回數列第n項的值。 說實話,這個題目整整花了我三天時間去思考(數學比較弱,大神見笑)#手動捂臉#。 其實到最後 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...