欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【设计模式】工厂方法模式 Factory Method Pattern

程序员文章站 2022-06-23 14:43:22
在简单工厂模式中产品的创建统一在工厂类的静态工厂方法中创建,体现了面形对象的封装性,客户程序不需要知道产品产生的细节,也体现了面向对象的单一职责原则(SRP),这样在产品很少的情况下使用起来还是很方便, 但是如果产品很多,并且不断的有新产品加入,那么就会导致静态工厂方法变得极不稳定,每次加入一个新产 ......

在中产品的创建统一在工厂类的静态工厂方法中创建,体现了面形对象的封装性,客户程序不需要知道产品产生的细节,也体现了面向对象的,这样在产品很少的情况下使用起来还是很方便, 但是如果产品很多,并且不断的有新产品加入,那么就会导致静态工厂方法变得极不稳定,每次加入一个新产品就要修改静态工厂方法,这违背了面向对象设计原则的。那么在应对这种不断增加的新产品,简单工模式有些力不从心了,那么什么模式可以完美应对呢?这就是这篇文章要谈到的工厂方法模式。在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂类,系统提供一个与产品等级结构对应的工厂等级结构。

一、工厂方法模式定义

工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。

二、工厂方法模式结构图

工厂方法模式结构图

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

输出结果:

 

四、重构音频播放器实例得到工厂方法模式

在中我们举了一个音频播放器的例子,开发人员从开始直接创建对象中逐步随着需求的改变最终得到了, 完美的解决了播放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();
    }
}

输出结果:

看起来很不错,完美的解决了播放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();
    }
}

运行软件输出结果:

代码重构完成,结构符合预期,在回过头来Review 一下代码,这不就是Factory Method Pattern吗? 这样开发人员就将这种场景下的代码构造的比较合理了。甲方再增加新的音频文件格式时,就很容易应对了,只需要创建一个具体产品并且再创建一个具体的工厂类来创建这个产品就可以了。这样软件更符合面向对象设计原则的 和原则了。

下来问题来了, 如果甲方提出需要这个播放器软件支持视频播放,开发人员应该怎么办能? 那么 随着学习其他模式就能找到更合理的答案。

五、工厂方法模式的优点:

  1. 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  2. 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
  3. 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性和灵活性也就变得非常好,维护起来就变得简单了,完全符合“”。

六、工厂方法模式的缺点:

  1. 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  2. 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到反射等技术,增加了系统的实现难度。

七、工厂方法模式的使用场景:

  1. 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
  2. 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。有了这么一个特点, 我们可以在软件的运行时改变系统的功能,进而实现热插拔。