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

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

1、工廠方法模式定義

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

2、工廠方法模式結構圖

image

工廠方法模式結構圖app

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

它是定義產品的接口,是工廠方法模式所建立對象的父類,也就是產品對象的公共父類,這個角色通常能夠有抽象類或者接口來擔當。學習

2.ConcreteProduct(具體產品):

它實現了抽象產品接口,某種類型的具體產品由專門的具體工廠建立,具體工廠和具體產品之間一一對應。測試

3.Factory(抽象工廠):

在抽象工廠類中,聲明瞭工廠方法(Factory Method),用於返回一個產品。抽象工廠是工廠方法模式的核心,全部建立具體對象的具體工廠類都必須實現該接口。spa

4. ConcreteFactory(具體工廠):

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

與簡單工廠模式相比,工廠方法模式最重要的區別是引入了抽象工廠角色,抽象工廠能夠是接口,也能夠是抽象類或者具體類3d

3、工廠方法模式代碼實現:

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();
    }
}

客戶端調用:code

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();
}

輸出結果:orm

image

 

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

簡單工廠模式中咱們舉了一個音頻播放器的例子,開發人員從開始直接建立對象中逐步隨着需求的改變最終獲得了簡單工廠模式, 完美的解決了播放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原則了。

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

5、工廠方法模式的優勢:

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

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

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

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

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

8、擴展-使用配置+反射動態建立特定工廠實現工廠熱替換

以上面的音樂播放器爲例, 若是在特定的場景下只須要播放MP3  格式的音樂,而在另外一些特定的場景下只須要播放ACC    格式的音樂, 怎麼辦呢?

這裏使用配置+反射動態建立 工廠的方式來實現這個需求, 首先來看怎麼實現僅支持MP3的狀況:

1. 在配置文件App.config中增長一個配置:

<appSettings>
	<add key="MethodFactory" value="DesignPattern.MehodFactory.AudioInstance.Mp3Factory"/>
</appSettings>
 

2.在調用處讀取上面的配置文件並使用反射獲得具體工廠,而後調用Play  方法,代碼以下:

static void AudioMethodFactoryExecuteBySetting()
{
    DesignPattern.MehodFactory.AudioInstance.IFactory factory=null;
    var setting = ConfigurationSettings.AppSettings["MethodFactory"];
    string dir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
    string[] assemblies = Directory.GetFiles(dir, "*.exe");
    List<Type> types = new List<Type>();
    foreach (var s in assemblies)
    {
        var ass=Assembly.LoadFile(s);
        foreach(Type t in ass.GetExportedTypes()){
            if(t.IsClass && typeof( DesignPattern.MehodFactory.AudioInstance.IFactory).IsAssignableFrom(t)){
                if(t.FullName==setting){
                    factory=Activator.CreateInstance(t) as DesignPattern.MehodFactory.AudioInstance.IFactory;                   
                }
            }
        }
    }   

    if (factory == null) return;

    IAudio audio = factory.Create();
    audio.Play("take me to your hert");
}

輸出結果:

image

若是需求改成僅支持ACC格式的音頻文件,就很容易實現了,僅僅須要修改配置文件中配置的具體工廠類的字符串就能夠了,其它任何地方都不須要改變,而且不須要編譯應用程序就能夠正常工做了。

這裏咱們找到應用程序生成的目錄:F:\source\DesignPattern\DesignPattern.MehodFactory\bin\Debug,在這裏咱們看到有下列文件:

image

咱們只須要用寫字板打開配置文件 DesignPattern.MehodFactory.exe.config  修改配置爲須要支持的ACCFactory就能夠了

image

而後雙擊文件DesignPattern.MehodFactory.exe運行,結果以下:

image

咱們看到僅僅只改變了一下配置就輕鬆實現了應用功能的熱替換,不須要作任何的編譯和代碼上的修改。

九.無源碼擴展

假如這個應用程序是從其它的軟件開發商那裏買來的,如今你的老闆然你開發一個新的功能,須要在某些場景下僅支持AAR格式的音頻文件,該怎麼辦呢?。

1.新建一個控制檯應用程序

假設如今沒有源代碼,可是還要實現支持AAR的音頻格式文件的播放, 首先須要從新建一個C# 的工程文件,建立一個控制檯應用程序,這裏我命名爲DesignPattern.Extension, 而後建立一個Aar class 並繼承IAudio接口,再建立一個AarFactory class並集成IFactory接口, 代碼以下:

public class AarFactory : DesignPattern.MehodFactory.AudioInstance.IFactory
{
    public IAudio Create()
    {
        return new Aar();
    }
}

public class Aar : IAudio
{
    public void Play(string name)
    {
        Console.WriteLine("Start playing Aar...");
        Console.WriteLine(string.Format("The song name is: [{0}.aar]", name));
        Console.WriteLine("..........");
    }
}

寫完代碼後編譯DesignPattern.Extension 應用程序而後找到生成的DesignPattern.Extension.exe 文件, 而後拷貝到F:\source\DesignPattern\DesignPattern.MehodFactory\bin\Debug, 以下:

image

2. 修改配置文件以下:

image

3. 雙擊DesignPattern.MehodFactory.exe 運行, 看到下面的結果輸出:

image

咱們看到輸出的正是Arr文件的邏輯。

這樣就輕鬆實現了無源碼的擴展。

 

注意:這裏我使用的是控制檯應用程序,其擴展名是.exe, 因此在反射的時候我掃碼的是當前工做目錄下的全部exe後綴的文件,若是是類庫工程,就要掃描當前工做目錄下的dll文件。而且還要將exe文件也掃描進去,否則當前程序集中實現的工廠沒法找到。

相關文章
相關標籤/搜索