代码改变世界

【设计模式】简单工厂模式 Simple Factory Pattern

2018-07-19 16:24 by 蓝之风, ... 阅读, ... 评论, 收藏, 编辑

简单工厂模式Simple Factory Pattern【Simple Factory Pattern】是设计模式里最简单的一个模式,又叫静态工厂模式【Static Factory Pattern】,这个模式没有收录在GOF 23 个模式中,因为他非常简单,在项目中使用也非常广泛,所以就用它来开篇。

一、简单工厂模式定义:

简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。

二、简单工厂模式结构图

image

简单工厂结构图

从简单工厂结构图中我们可以看出它包含三个角色:

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

它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。抽象产品可以使用接口和抽象类来来实现。

2. ConcreteProduct (具体产品角色):

它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法。在简单工厂模式中,客户端通过工厂类来创建一个产品类的实例,而无须直接使用new关键字来创建对象,它是工厂模式家族中最简单的一员。 在使用简单工厂模式时,首先需要对产品类进行重构,不能设计一个包罗万象的产品类,而需根据实际情况设计一个产品层次结构,将所有产品类公共的代码移至抽象产品类,并在抽象产品类中声明一些抽象方法,以供不同的具体产品类来实现。

3. Factory (工厂角色):

这个角色就是工厂类,他是工厂模式的核心,负责实现创建所有产品实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product。

三、简单工厂的代码实现

public interface IProduct
{
    void SomeMethod();
}

public class ConcreteProductA : IProduct
{
    public void SomeMethod()
    {
        Console.WriteLine("I am product A");
    }
}

public class ConcreteProductB : IProduct
{
    public void SomeMethod()
    {
        Console.WriteLine("I am product B");
    }
}
public class Factory
{
    public static IProduct Create(string productType)
    {
        IProduct product;
        switch (productType.ToUpper())
        {
            case "A":
                product = new ConcreteProductA();
                break;
            case "B":
                product = new ConcreteProductB();
                break;
            default:
                throw new ArgumentException("Invlid Argument.", nameof(productType));
        }
        return product;
    }
}

客户端调用:

static void Main()
{
    var product = Factory.Create("A");
    product.SomeMethod();

    product = Factory.Create("B");
    product.SomeMethod();

    Console.ReadKey();
}

输出结果:

image

 

四、需求改变促使代码重构得到简单工厂模式

甲方提出了这样一个需求:要求做一个音频播放器。只需要支持播放WMA格式的文件就好了。开发人员拿到需求后窃喜,这么简单个需求,简直就是毛毛雨啦。好了编码开始。

很快代码就编写好了:

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

[Description("1.2. Simple Factory")]
public class App
{
    static void Main()
    {
        Audio audio=new Audio();
        audio.Play("take me to your heart");

        Console.ReadKey();
    }
}

输出结果如下:

image

甲方看到软件试用一翻感觉不错很满意,赶紧给开发人员支付了开发费用。将软件投放市场。

很长一段时间,市场放映不错,这个音频播放器市场占有率提高很快。但是有一天一种新的音频格式诞生了,WAV格式的音频文件越来越多了,用户拿到WAV的音乐没办法使用这个播放器播放了,甲方从市场上得到了很多反馈,于是,甲方决定升级这款音频播放器软件使其支持这种新的WAV格式的音频文件。 甲方找到了开发人员要求增加WAV格式文件的播放。开发人员拿到需求后赶紧开始写代码。很快代码写出来了:

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

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

[Description("1.2. Simple Factory")]
public class App
{
    static void Main()
    {
        Console.WriteLine("Please input a or m");
        var input = Console.ReadKey();
        if (input != null)
        {
            if (input.Key == ConsoleKey.A)
            {
                WavFile wav = new WavFile();
                wav.Play("take me to your heart.wav");
            }
            else if (input.Key == ConsoleKey.M)
            {
                WmaFile audio = new WmaFile();
                audio.Play("take me to your heart.wma");
            }
        }


        Console.ReadKey();
    }
}

运行软件如图:

image

甲方拿到软件测试一翻,很满意,赶紧投入市场,反响强烈,市场占有率进一步提高。

随着时间的推移一种新的音乐格式出现了,mp3 格式文件的音乐出现了,他的文件占用存储小, 音质好,原来一张光盘只能装5首wma或者wav格式的音乐文件,现在一张光盘可以装30首mp3了。这简直就是一场革命, 但是甲方目前的音频播放播放器不支mp3格式, 甲方的几个竞争对手已经抢先一步支持了mp3的播放,甲方的音频播放器的市场占有率在急剧下降,甲方坐不住了,他来不及从市场上去拿反馈了,赶紧找到开发人员,让开发人员赶紧加班加点开发出支持mp3格式文件,不然他的播放器就要被这个瞬息万变的市场所淘汰。开发人员接到这个命令也是不敢怠慢。赶紧投入工作。

当开发人员打开已经实现的代码时陷入了沉思,再加一个类开发mp3 格式文件的播放功能吗? 如果这样写下去的话代码会变得很难维护和扩展了,现在技术发展这么快,指不定哪一天有冒出一个什么格式,再这样堆积木似的一直写下去自己都不想看自己写的代码了。 于是他决定重构代码,但是怎么重构呢? 突然脑海中闪过了昨天不是在看《设计模式》吗?里面不是有个简单工厂模式吗?这个模式似乎很适合中中场景,好的就用这个模式, 把播放音乐的功能提出一个接口, 然后创建出继承这个接口的各个格式文件的具体播放类,然后在创建一个静态工厂根据客户的音乐文件格式选择创建那个具体的类来播放当前的文件。 有了这个思路那就赶紧动手重构代码吧。 很快代码重构完成了:

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

好了这个音频播放器支持mp3了, 甲方非常高兴,赶紧把这个新的版本投入市场。甲方通过这次升级挽回了一些他在音频播放市场上的一些颓势。

通过这么一次突然的袭击使开发人员收获不少,最起码学到了一种设计模式(simple facfory)的使用,也是成就满满。 但是开发人员还是心有余悸,如果哪一天突然有出来个什么新的格式是不是又要加班加点的搞上一阵子。 他想如果在不改变这个软件已有代码的情况下,来扩展,比方说,新增了一个类型,但是不需要修改代码通过写一个类或者增加一些配置就可以实现这个完美支持新的格式的音频文件?(这不是OCP原则吗? ) 那么这个问题Simple Factory Pattern 能做到吗? 问题先留着,随着模式学习的深入, 让我们慢慢在后面的学习中去寻找答案。

五、简单工厂模式的优点

  1. 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,客户程序不需要产品的具体生产细节;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
  2. 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,不需要知道产品是怎么创建的。
  3. 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
  4. 简单工厂将创建产品的职责放在了单独的静态工厂中去处理和维护一定长度上体现了SRP原则

六、简单工厂模式的缺点

  1. 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
  2. 使用简单工厂模式将会增加系统中具体产品类的个数,在一定程序上增加了系统的复杂度和理解难度。
  3. 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
  4. 由于在增加新的产品的时候简单工厂模式必须要修改静态的工厂方法,所以它违背了OCP原则

七、工厂模式的使用场景

在以下情况下可以使用简单工厂模式:

  1. 工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
  2. 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。