【设计模式】工厂方法模式 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文件也扫描进去,否则当前程序集中实现的工厂没法找到。

相关文章
相关标签/搜索