用C#(.NET Core) 實現簡單工廠和工廠方法設計模式

来源:https://www.cnblogs.com/cgzl/archive/2018/04/09/8760250.html
-Advertisement-
Play Games

用C#(.NET Core) 實現簡單工廠(簡單工廠不是設計模式)和工廠方法模式 ...


本文源自深入淺出設計模式. 只不過我是使用C#/.NET Core實現的例子.

 

前言

當你看見new這個關鍵字的時候, 就應該想到它是具體的實現.

這就是一個具體的類, 為了更靈活, 我們應該使用的是介面(interface).

有時候, 你可能會寫出這樣的代碼:

這裡有多個具體的類被實例化了, 是根據不同情況在運行時被實例化的. 

當你看到這樣的代碼, 你就會知道當有需求需要對其進行修改或者擴展的時候, 你就得把這個文件打開, 然後看看在這裡應該添加或者刪除點什麼. 這類的代碼經常會分散在程式的多個地方, 這維護和更新起來就很麻煩而且容易出錯.

針對interface進行編程的時候, 你知道可以把自己獨立於系統未來可能要發生的變化. 為什麼呢? 因為如果你針對interface編程, 那麼對於任何實現了該介面的具體類對你來說都可以用, 多態嗎.

項目原始需求

有一個前沿的披薩店, 做披薩, 下麵是訂購披薩的類:

new一個披薩, 然後按照工序進行加工 最後返回披薩.

但是, 一個披薩店不可能只有一種披薩, 可能會有很多中披薩, 所以你可能會這樣修改代碼:

根據傳入的類型, 創建不同的披薩, 然後加工返回.

然後問題來了, 隨著時間的推移, 一個披薩店會淘汰不暢銷的披薩並添加新品種披薩.

使用上面的代碼就會出現這個問題, 針對需求變化, 我不得不把OrderPizza的部分代碼改來改去:

從這裡, 我們也可以看到, 上半部分是會變化的部分, 下半部分是不變的部分, 所以它們應該分開(把變化的部分和不變的部分分開, 然後進行封裝).

結構應該是這樣的:

右上角是變化的部分, 把這部分封裝到一個對象里, 它就是用來創建披薩的對象, 我們把這個對象叫做: 工廠.

工廠負責創建對象的細節工作. 我們創建的這個工廠叫做SimplePizzaFactory, 而orderPizza()這個方法就是該工廠的一個客戶(client).

任何時候客戶需要披薩的時候, 披薩工廠就會給客戶創建一個披薩.

接下來, 我們就建立這個簡易的披薩工廠:

就是通過傳入的類型參數, 建立並返回不同類型的披薩.

這樣我們就把披薩創建的工作封裝到了一個類裡面, 發生變化的時候, 只需要修改這一個類即可.

註意: 有時候上面這種簡單工廠可以使用靜態方法, 但是這樣也有缺點, 就是無法通過繼承來擴展這個工廠了.

回來修改PizzaStore這個類:

工廠是從構造函數傳入的, 併在PizzaStore裡面保留一個引用.

在OrderPizza()方法裡面, 我們使用工廠的創建方法代替了new關鍵字, 所以在這裡沒有具體的實例化.

簡單工廠的定義

簡單/簡易工廠並不是一個設計模式, 更多是一個編程習慣. 但是使用的非常廣泛.

簡單工廠類圖:

這個很簡單, 就不解釋了. 

簡單工廠就到這, 下麵要講兩個重量級的工廠模式.

用C#/.NET Core實現簡單工廠

Pizza父類:

using System;
using System.Collections.Generic;

namespace SimpleFactory.Pizzas
{
    public abstract class Pizza
    {
        public string Name { get; protected set; }
        public string Dough { get; protected set; }
        public string Sauce { get; protected set; }
        protected List<string> Toppings = new List<string>();

        public void Prepare()
        {
            Console.WriteLine($"Preparing: {Name}");
            Console.WriteLine($"Tossing: {Dough}");
            Console.WriteLine($"Adding sauce: {Sauce}");
            Console.WriteLine("Adding toppings: ");
            Toppings.ForEach(x => Console.WriteLine($"  {x}"));
        }

        public void Bake()
        {
            Console.WriteLine("Bake for 25 minutes");
        }

        public void Cut()
        {
            Console.WriteLine("Cutting the pizza into diagnol slices");
        }

        public void Box()
        {
            Console.WriteLine("Placing pizza in official PizzaStore box......");
        }
    }
}
View Code

各種Pizza:

namespace SimpleFactory.Pizzas
{
    public class CheesePizza: Pizza
    {
        public CheesePizza()
        {
            Name = "Cheese Pizza";
            Dough = "Think Dough";
            Sauce = "Salad";
            Toppings.Add("Grated Reggiano Cheese");
        }
    }
}

namespace SimpleFactory.Pizzas
{
    public class ClamPizza: Pizza
    {
        public ClamPizza()
        {
            Name = "Clam Pizza";
            Sauce = "Tomato sauce";
            Dough = "Soft dough";
            Toppings.Add("Shrimp meat");
        }
    }
}

namespace SimpleFactory.Pizzas
{
    public class PepperoniPizza: Pizza
    {
        public PepperoniPizza()
        {
            Name = "Pepperoni Pizza";
            Dough = "Thin dough";
            Sauce = "Black pepper";
            Toppings.Add("Beef Granules");
            Toppings.Add("Niblet");
        }
    }
}
View Code

簡單工廠:

using SimpleFactory.Pizzas;

namespace SimpleFactory
{
    public class SimplePizzaFactory
    {
        public Pizza CreatePizza(string type)
        {
            Pizza pizza = null;
            switch (type)
            {
                case "cheese":
                    pizza = new CheesePizza();
                    break;
                case "pepperoni":
                    pizza = new PepperoniPizza();
                    break;
                case "clam":
                    pizza = new ClamPizza();
                    break;
            }

            return pizza;
        }
    }
}

PizzaStore:

using SimpleFactory.Pizzas;

namespace SimpleFactory
{
    public class PizzaStore
    {
        private readonly SimplePizzaFactory _factory;

        public PizzaStore(SimplePizzaFactory factory)
        {
            _factory = factory;
        }

        public Pizza OrderPizza(string type)
        {
            var pizza = _factory.CreatePizza(type);
            pizza.Prepare();
            pizza.Bake();
            pizza.Cut();
            pizza.Box();
            return pizza;
        }
    }
}

測試運行:

using System;

namespace SimpleFactory
{
    class Program
    {
        static void Main(string[] args)
        {
            var pizzaStore = new PizzaStore(new SimplePizzaFactory());
            var cheesePizza = pizzaStore.OrderPizza("cheese");
            Console.WriteLine();
            var clamPizza = pizzaStore.OrderPizza("pepperoni");
            Console.ReadKey();
        }
    }
}

 

需求變更 - 授權連鎖披薩店

 披薩店開的很好, 所以老闆在全國各地開授權連鎖分店了, 而每個地點的分店根據當地居民的口味, 它們所提供的披薩種類可能會不同.

例如紐約和芝加哥和加利福尼亞的就有可能不同.

針對這個需求, 我們可能會想到的第一種辦法就是: 把SimplePizzaFactory抽取出來, 分別建立三個地點的工廠, 然後根據地點把相應的工廠組合到PizzaStore

代碼是這樣的:

紐約:

芝加哥:

因為個連鎖店分佈在各地, 老闆想做質量管控: 做披薩的基本工序應該是一樣的, 但是針對某種披薩各地可以有不同的風格做法.

所以我們把createPizza()方法放回到PizzaStore, 但這次它是抽象方法, 然後各地都會創建自己的PIzzaStore:

下麵是紐約和芝加哥的披薩店:

針對每種披薩, 紐約和芝加哥可能會有自己風格具體實現的披薩.

orderPizza()方法是在父類/抽象類裡面實現的, 這裡的披薩還是抽象的, 所以它並不知道是PizzaStore的哪個子類來做的披薩.

代碼運行的時候, orderPizza()會調用createPizza()方法, PizzaStore的某個子類肯定會對此負責.

所以你哪個地方的PizzaStore, 就會決定產出的是哪個地方特產的披薩.

 

下麵就創建PizzaStore, 例如紐約的:

其他地點的都差不多, 就不貼圖了.

如何聲明一個工廠方法

還是看這張圖:

抽象的PizzaStore把訂購披薩的固定工序orderPizza()放在了抽象類裡面.

創建披薩createPizza()方法是在各地的披薩店裡做實現.

用一行代碼來解釋工廠方法就是:

工廠方法是讓其子類具體來實現對象創建的工作. 這樣就把父類中的客戶代碼和子類的創建對象部分的代碼解耦了.

 

上面工作做的挺好, 但是還差一件事....披薩.

首先抽象父類:

裡面定義了調味料和工序

然後具體的披薩:

紐約的乳酪披薩

芝加哥的乳酪披薩

最後運行一下:

 

工廠方法模式

所有的工廠模式都會封裝對象的創建過程, 而工廠方法模式把對象創建的動作交給了子類, 並讓它決定創建哪些對象.

創建者:

 

產品:

看看另外一種結構 -- 並行的類結構:

 

工廠方法模式的定義:

工廠方法模式定義了一個創建對象的介面, 但是讓子類來決定具體創建的是哪一個對象. 工廠方法讓一個類延遲實例化, 直到子類的出現.

左邊是產品, 所有具體的產品都應該繼承於同一個父類/介面.

右邊的Creator類裡面包含所有方法的實現除了抽象的工廠方法. 這個抽象的工廠方法在Creator的子類裡面必須進行實現, 產品就是在子類具體實現的工廠方法裡面創造出來的.

 

設計原則 -- 應該依賴於抽象, 而不依賴於具體的類

這就是著名的: DIP (Dependency Inversion Principle) 依賴反轉原則.

進一步解釋就是: 高級別的組件不應該依賴於低級別的組件, 它們都應該依賴於抽線.

高級別組件, 就是它有一組行為定義在另外一堆低級別的組件裡面了.

例如PizzaStore就是高級別的, 具體的披薩就是低級別的.

應該該設計原則後:

這時它們都依賴於抽象的披薩父類了.

實現該原則的三點指導建議

  • 沒有變數引用具體的類(可已使用工廠代替創建這個具體的類)
  • 沒有類派生於具體的類(派生於它就依賴於它)
  • 不去重寫(override)其任一父類的已實現方法(如果重寫了, 那麼這個類並不適合作為起始的抽象類, 因為基類裡面的方法本應該是共用與所有子類的)

和其它原則一樣, 只是儘力去按照這三點建議去執行, 並不是必須一直要這麼做.

C#/.NET Core的代碼實現

各種pizza:

namespace FactoryMethodPattern.Pizzas
{
    public class ChicagoCheesePizza : Pizza
    {
        public ChicagoCheesePizza()
        {
            Name = "Chicago Cheese Pizza";
            Dough = "Think Dough 1";
            Sauce = "Salad 1";
            Toppings.Add("Grated Reggiano Cheese 1");
        }
    }
}

namespace FactoryMethodPattern.Pizzas
{
    public class ChicagoClamPizza : Pizza
    {
        public ChicagoClamPizza()
        {
            Name = "Chicago Clam Pizza";
            Sauce = "Tomato sauce 1";
            Dough = "Soft dough 1";
            Toppings.Add("Shrimp meat 1");
        }
    }
}

namespace FactoryMethodPattern.Pizzas
{
    public class ChicagoPepperoniPizza : Pizza
    {
        public ChicagoPepperoniPizza()
        {
            Name = "Chicago Pepperoni Pizza";
            Dough = "Thin dough 1";
            Sauce = "Black pepper 1";
            Toppings.Add("Beef Granules 1");
            Toppings.Add("Niblet 1");
        }
    }
}

namespace FactoryMethodPattern.Pizzas
{
    public class NYCheesePizza: Pizza
    {
        public NYCheesePizza()
        {
            Name = "NY Cheese Pizza";
            Dough = "Think Dough 2";
            Sauce = "Salad 2";
            Toppings.Add("Grated Reggiano Cheese 2");
        }
    }
}

namespace FactoryMethodPattern.Pizzas
{
    public class NYClamPizza: Pizza
    {
        public NYClamPizza()
        {
            Name = "NY  Clam Pizza";
            Sauce = "Tomato sauce 2";
            Dough = "Soft dough 2";
            Toppings.Add("Shrimp meat 2");
        }
    }
}

namespace FactoryMethodPattern.Pizzas
{
    public class NYPepperoniPizza: Pizza
    {
        public NYPepperoniPizza()
        {
            Name = "NY Pepperoni Pizza";
            Dough = "Thin dough 2";
            Sauce = "Black pepper 2";
            Toppings.Add("Beef Granules 2");
            Toppings.Add("Niblet 2");
        }
    }
}
View Code

披薩店抽象父類:

using FactoryMethodPattern.Pizzas;

namespace FactoryMethodPattern
{
    public abstract class PizzaStore
    {
        public Pizza OrderPizza(string type)
        {
            var pizza = CreatePizza(type);
            pizza.Prepare();
            pizza.Bake();
            pizza.Cut();
            pizza.Box();
            return pizza;
        }

        protected abstract Pizza CreatePizza(string type);
    }
}

Chicago披薩店:

using FactoryMethodPattern.Pizzas;

namespace FactoryMethodPattern
{
    public class ChicagoPizzaStore: PizzaStore
    {
        protected override Pizza CreatePizza(string type)
        {
            Pizza pizza = null;
            switch (type)
            {
                case "cheese":
                    pizza = new ChicagoCheesePizza();
                    break;
                case "pepperoni":
                    pizza = new ChicagoPepperoniPizza();
                    break;
                case "clam":
                    pizza = new ChicagoClamPizza();
                    break;
            }

            return pizza;
        }
    }
}

紐約披薩店:

using FactoryMethodPattern.Pizzas;

namespace FactoryMethodPattern
{
    public class NYPizzaStore : PizzaStore
    {
        protected override Pizza CreatePizza(string type)
        {
            Pizza pizza = null;
            switch (type)
            {
                case "cheese":
                    pizza = new NYCheesePizza();
                    break;
                case "pepperoni":
                    pizza = new NYPepperoniPizza();
                    break;
                case "clam":
                    pizza = new NYClamPizza();
                    break;
            }

            return pizza;
        }
    }
}
View Code

測試運行:

using System;

namespace FactoryMethodPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var nyStore = new NYPizzaStore();
            var chicagoStore = new ChicagoPizzaStore();

            var pizza = nyStore.OrderPizza("cheese");
            Console.WriteLine($"Ordered a {pizza.Name} in NY");
            Console.WriteLine();
            var pizza2 = chicagoStore.OrderPizza("cheese");
            Console.WriteLine($"Ordered a {pizza2.Name} in Chicago");

            Console.ReadKey();
        }
    }
}

 


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

-Advertisement-
Play Games
更多相關文章
  • 閱讀目錄 一般形式 不含參數列表 含參數列表 閱讀目錄 閱讀目錄 一般形式 不含參數列表 含參數列表 一般形式 不含參數列表 含參數列表 一、一般形式 this 有兩種形式: 1)不含參數列表,例如:this.age , this.speak() , this 等等 2)含參數列表,例如:this( ...
  • leetcode,編程題,二叉樹,中序遍歷,非遞歸實現,迭代實現 ...
  • 配置指令 (。。。) 錯誤日誌 (。。。) 異常處理 為什麼異常處理很方便 (。。。) PHP的異常處理實現 (。。。) SPL異常 (。。。) ...
  • 多態是C++的三大法器之一,此處我們用C模擬多態,加深對C++的多態的理解 ...
  • 聲明:以下轉載自:Java中的File文件類詳解 文件操作在Java的io操作中占有十分重要的地位,本文從以下幾個方面來接受Java中對文件的操作。 1.Java中新建或者刪除一個文件,文件夾以及createNewFile(),delete(),mkdir(),mkdirs()函數的使用。 2. 判 ...
  • using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; namespace ConsoleApplication2{ class P ...
  • 註意:無論那種方法,都要先設置Tab控制項的Style屬性為fixed width.第一種方法MFC,tabcontrol控制項改變標簽大小 - CSDN博客 https://blog.csdn.net/u012702039/article/details/22668161CSize size, siz... ...
  • using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; namespace ConsoleApplication1 { class ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...