【設計模式】工廠方法模式 Factory Method Pattern

来源:https://www.cnblogs.com/vaiyanzi/archive/2018/07/24/9359474.html
-Advertisement-
Play Games

在簡單工廠模式中產品的創建統一在工廠類的靜態工廠方法中創建,體現了面形對象的封裝性,客戶程式不需要知道產品產生的細節,也體現了面向對象的單一職責原則(SRP),這樣在產品很少的情況下使用起來還是很方便, 但是如果產品很多,並且不斷的有新產品加入,那麼就會導致靜態工廠方法變得極不穩定,每次加入一個新產 ...


簡單工廠模式中產品的創建統一在工廠類的靜態工廠方法中創建,體現了面形對象的封裝性,客戶程式不需要知道產品產生的細節,也體現了面向對象的單一職責原則(SRP),這樣在產品很少的情況下使用起來還是很方便, 但是如果產品很多,並且不斷的有新產品加入,那麼就會導致靜態工廠方法變得極不穩定,每次加入一個新產品就要修改靜態工廠方法,這違背了面向對象設計原則的開閉原則(OCP)。那麼在應對這種不斷增加的新產品,簡單工模式有些力不從心了,那麼什麼模式可以完美應對呢?這就是這篇文章要談到的工廠方法模式。在工廠方法模式中,我們不再提供一個統一的工廠類來創建所有的產品對象,而是針對不同的產品提供不同的工廠類,系統提供一個與產品等級結構對應的工廠等級結構。

一、工廠方法模式定義

工廠方法模式(Factory Method Pattern):定義一個用於創建對象的介面,讓子類決定將哪一個類實例化。工廠方法模式讓一個類的實例化延遲到其子類。工廠方法模式又簡稱為工廠模式(Factory Pattern),又可稱作虛擬構造器模式(Virtual Constructor Pattern)或多態工廠模式(Polymorphic Factory Pattern)。

二、工廠方法模式結構圖

image

工廠方法模式結構圖

1.IProduct (抽象產品角色):

它是定義產品的介面,是工廠方法模式所創建對象的父類,也就是產品對象的公共父類,這個角色一般可以有抽象類或者介面來擔當。

2.ConcreteProduct(具體產品):

它實現了抽象產品介面,某種類型的具體產品由專門的具體工廠創建,具體工廠和具體產品之間一一對應。

3.Factory(抽象工廠):

在抽象工廠類中,聲明瞭工廠方法(Factory Method),用於返回一個產品。抽象工廠是工廠方法模式的核心,所有創建具體對象的具體工廠類都必須實現該介面。

4. ConcreteFactory(具體工廠):

它是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,並可由客戶端調用,返回一個具體產品類的實例。

與簡單工廠模式相比,工廠方法模式最重要的區別是引入了抽象工廠角色,抽象工廠可以是介面,也可以是抽象類或者具體類

三、工廠發發模式代碼實現:

public interface IProduct
{
    void DoSomething();
}
public interface IFactory
{
    IProduct Create();
}
public class ConcreteProductA : IProduct
{
    public void DoSomething()
    {
        Console.WriteLine("I'm Product A");
    }
}
public class ConcreteProductB : IProduct
{
    public void DoSomething()
    {
        Console.WriteLine("I'm Product B");
    }
}
public class ConcreteFactoryA : IFactory
{
    public IProduct Create()
    {
        return new ConcreteProductA();
    }
}
public class ConcreteFactoryB : IFactory
{
    public IProduct Create()
    {
        return new ConcreteProductB();
    }
}

客戶端調用:

static void Main()
{
    //使用ConcreteFactoryA 創建 ProductA
    IFactory factoryA = new ConcreteFactoryA();
    IProduct productA = factoryA.Create();
    productA.DoSomething();

    //使用ConcreteFactoryB 創建 ProductB
    IFactory factoryB = new ConcreteFactoryB();
    IProduct productB = factoryB.Create();
    productB.DoSomething();

    Console.ReadKey();
}

輸出結果:

image

 

四、重構音頻播放器實例得到工廠方法模式

簡單工廠模式中我們舉了一個音頻播放器的例子,開發人員從開始直接創建對象中逐步隨著需求的改變最終得到了簡單工廠模式, 完美的解決了播放MP3,WAV,WMA格式的音頻文件。最終代碼看起來是這樣:

public interface IAudio
{
    void Play(string name);
}

public class Wma : IAudio
{
    public void Play(string name)
    {
        Console.WriteLine("Start playing wma file...");
        Console.WriteLine($"The song name is: [{name}.wma]");
        Console.WriteLine("..........");
    }
}
public class Wav : IAudio
{
    public void Play(string name)
    {
        Console.WriteLine("Start playing wav file...");
        Console.WriteLine($"The song name is: [{name}.wav]");
        Console.WriteLine("..........");
    }
}
public class Mp3 : IAudio
{
    public void Play(string name)
    {
        Console.WriteLine("Start playing mp3...");
        Console.WriteLine($"The song name is: [{name}.mp3]");
        Console.WriteLine("..........");
    }
}


public class AudioFactory
{
    public static IAudio Create(string songType)
    {
        IAudio audio;
        switch (songType.ToUpper())
        {
            case "A":
                audio = new Wav();
                break;
            case "M":
                audio = new Wma();
                break;
            case "P":
                audio = new Mp3();
                break;
            default:
                throw new ArgumentException("Invalid argument", nameof(songType));
        }

        return audio;
    }
}

[Description("1.2. Simple Factory")]
public class App
{
    static void Main()
    {
        Console.WriteLine("Please input a or m or p");
        var input = Console.ReadKey();
        if (input != null)
        {
            IAudio audio = AudioFactory.Create(input.Key.ToString());
            audio.Play("take me to your hert");
        }

        Console.ReadKey();
    }
}

輸出結果:

image

看起來很不錯,完美的解決了播放WMA,WAV和MP3 格式的音頻文件,但是音樂文件的格式不斷在發展增多,因此播放器也要通過不斷的升級來支持不斷涌現的新格式的音頻文件。 甲方已經提出來了支持MPEG, MPEG-4 等等格式的文件,每次開發人員都要新增一個具體的音頻格式的類,並且在工廠的靜態方法中創建一個case條件來支持新的格式文件。日積月累,隨著時間的推移,swich case 的邏輯變得異常的龐大和複雜,很難維護了,這不,最近甲方提出來要支持acc格式文件的播放,這次升級終於是產生了一次事故, 開發人員從甲方哪裡拿到要支持acc音頻格式的文件需求,輕車熟路創建了個acc的產品文件類,但是忘記在swich case 中加這個case就將代碼編譯打包提交給甲方。由於甲方和開發人員過去每次配合的都很好,這一次他就絕對的信任了開發人員,於是沒有測試新的版本就直接發佈到市場上投入了商業使用。結果可想而知根本就播放不了acc格式的音頻文件。 甲方知道此事後很生氣,勒令開發人員立馬修複bug重新發佈版本,但是市場是瞬息萬變的,就因為這麼一個失誤的發佈,市場上的竟品軟體就很快蠶食了甲方播放器的市場。開發人員不敢怠慢,加班加點,找出bug並修複重新打包交付甲方,甲方趕緊將新版本經過充分測試後投入到市場。

隨後開發人員準備找出容易出現這種錯誤原因,將這種犯錯的機會扼殺在搖籃。除了自身的粗心之外,他還想從代碼上找到一些原因。於是他Review了一下自己的代碼, 他發現工廠類中的靜態工廠方法的邏輯太複雜了,翻滾了好幾個屏幕,看了一個多小時才把這裡面的代碼理順看清楚了, 看完後發發現靜態工廠方法的職責隨著產品的增多在不斷的增多, 工廠方法的負擔太重了, 他決定重構這個地方的代碼,他期望將創建具體產品的職責單提取到獨的一個類中來完成,一個類負責一個具體產品的創建,於是他提出了個這個創建具體產品的抽象介面IFactory, 然後讓具體創建類都繼承自這個介面, 通過重構代碼,現在音頻播放器的代碼變成了這樣:

public interface IAudio
{
    void Play(string name);
}
public interface IFactory
{
    IAudio Create();
}
public class Wma : IAudio
{
    public void Play(string name)
    {
        Console.WriteLine("Start playing wma file...");
        Console.WriteLine($"The song name is: [{name}.wma]");
        Console.WriteLine("..........");
    }
}
public class Wav : IAudio
{
    public void Play(string name)
    {
        Console.WriteLine("Start playing wav file...");
        Console.WriteLine($"The song name is: [{name}.wav]");
        Console.WriteLine("..........");
    }
}
public class Mp3 : IAudio
{
    public void Play(string name)
    {
        Console.WriteLine("Start playing mp3...");
        Console.WriteLine($"The song name is: [{name}.mp3]");
        Console.WriteLine("..........");
    }
}

public class Acc : IAudio
{
    public void Play(string name)
    {
        Console.WriteLine("Start playing Acc...");
        Console.WriteLine($"The song name is: [{name}.acc]");
        Console.WriteLine("..........");
    }
}

public class WmaFactory : IFactory
{
    public IAudio Create()
    {
        return new Wma();
    }
}

public class WavFactory : IFactory
{
    public IAudio Create()
    {
        return new Wav();
    }
}

public class Mp3Factory : IFactory
{
    public IAudio Create()
    {
        return new Mp3();
    }
}

public class AccFactory : IFactory
{
    public IAudio Create()
    {
        return new Acc();
    }
}

[Description("2.1. Factory Mothed payer")]
public class App
{
    static void Main()
    {
        //Wma play
        IFactory wmaFactory = new WmaFactory();
        IAudio wamAudio = wmaFactory.Create();
        wamAudio.Play("take me to your hert");
        //Wav play
        IFactory wavFactory = new WavFactory();
        IAudio wavAudio = wavFactory.Create();
        wavAudio.Play("take me to your hert");
        //Mp3 play
        IFactory mp3Factory = new Mp3Factory();
        IAudio mp3Audio = mp3Factory.Create();
        mp3Audio.Play("take me to your hert");
        //Acc play
        IFactory accFactory = new AccFactory();
        IAudio accAudio = accFactory.Create();
        accAudio.Play("take me to your hert");

        Console.ReadKey();
    }
}

運行軟體輸出結果:

image

代碼重構完成,結構符合預期,在回過頭來Review 一下代碼,這不就是Factory Method Pattern嗎? 這樣開發人員就將這種場景下的代碼構造的比較合理了。甲方再增加新的音頻文件格式時,就很容易應對了,只需要創建一個具體產品並且再創建一個具體的工廠類來創建這個產品就可以了。這樣軟體更符合面向對象設計原則的SRPOCP原則了。

下來問題來了, 如果甲方提出需要這個播放器軟體支持視頻播放,開發人員應該怎麼辦能? 那麼 隨著學習其他模式就能找到更合理的答案。

五、工廠方法模式的優點:

  1. 在工廠方法模式中,工廠方法用來創建客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被實例化這一細節,用戶只需要關心所需產品對應的工廠,無須關心創建細節,甚至無須知道具體產品類的類名。
  2. 基於工廠角色和產品角色的多態性設計是工廠方法模式的關鍵。它能夠讓工廠可以自主確定創建何種產品對象,而如何創建這個對象的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱為多態工廠模式,就正是因為所有的具體工廠類都具有同一抽象父類。
  3. 使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的介面,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就可以了,這樣,系統的可擴展性和靈活性也就變得非常好,維護起來就變得簡單了,完全符合“開閉原則(OCP)”。

六、工廠方法模式的缺點:

  1. 在添加新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度,有更多的類需要編譯和運行,會給系統帶來一些額外的開銷。
  2. 由於考慮到系統的可擴展性,需要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能需要用到反射等技術,增加了系統的實現難度。

七、工廠方法模式的使用場景:

  1. 客戶端不知道它所需要的對象的類。在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建,可將具體工廠類的類名存儲在配置文件或資料庫中。
  2. 抽象工廠類通過其子類來指定創建哪個對象。在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的介面,而由其子類來確定具體要創建的對象,利用面向對象的多態性和里氏代換原則,在程式運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。有了這麼一個特點, 我們可以在軟體的運行時改變系統的功能,進而實現熱插拔。

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

-Advertisement-
Play Games
更多相關文章
  • JS實現文本中查找並替換字元 效果圖: 代碼如下,複製即可使用: 如有錯誤,歡迎聯繫我改正,非常感謝!!! ...
  • <!DOCTYPE html> 1、定義: DOCTYPE標簽是一種標準通用標記語言的文檔類型聲明,它的目的是要告訴標準通用標記語言解析器,它應該使用什麼樣的文檔類型定義(DTD)來解析文檔。<!DOCTYPE> 聲明必須是 HTML 文檔的第一行,位於 <html> 標簽之前。 2、作用: 聲明文 ...
  • functionName(parameter1, parameter2, parameter3) { // 要執行的代碼…… } 參數規則 JavaScript 函數定義時形參沒有指定數據類型。 JavaScript 函數對實參的類型不會進行檢測。 JavaScript 函數對實參的個數不會進行檢測 ...
  • 一、引入 相信很多人都遇到過敏感信息需要做部分隱藏功能,大多數都是用特殊符號去替換。 正好今天我又遇到這樣的前端顯示的需求,正好把相關JS記錄下來,方便下次再用。 二、JS部分 三、應用實例 1 <!doctype html> 2 <html lang="en"> 3 <head> 4 <meta ...
  • 前言 上一篇文章以v-on指令綁定click事件為例介紹了v-on指令的使用方法,本文介紹一下v-on綁定事件的一些屬性的使用方法。 v-on綁定指令屬性 .stop屬性 阻止單擊事件繼續向上傳播(簡單點說就是不讓父節點及父節點以上的節點事件觸發),本示例如果沒有stop屬性,父節點和爺爺節點事件將 ...
  • // 6.2.4 組合使用構造函數和原型模式————創建自定義對象的方法: function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "C ...
  • <!-- 解決圖片旋轉 --> <script src="/libs/jquery/jquery.min.js"></script> <script src="/libs/exif/exif.js"></script> // html <input type="file" accept="image ...
  • 緩存這個東西相信大家工作中都接觸得比較多,相應的在不同場景下也會遇到各種各樣的問題。下麵我列舉幾種可能會遇到的問題並提供一些解決建議。 1、如何把海量數據存放在緩存中並提供快速查詢 現實中我們的緩存通常都是以string,map,array,list,set,tree等具體的類型或者集合存放記憶體中, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...