二十三种设计模式[1] - 工厂方法(Factory Method)

前言

       工厂方法,又名工厂模式,属于创建型模式。

       其目的是通过定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实例化延迟到子类。

       所以,当你不知道你必须要创建的对象的类型时或者你希望在程序运行时决定你需要创建的类型时,可以考虑工厂方法。

结构

FactoryMethod_1

需要角色如下:

  • IProduct(产品接口):工厂方法生产的产品接口;
  • Product(产品):产品接口的实现类;
  • IFactory(工厂接口):承载了工厂方法的接口;
  • ConcreteFactory(工厂):工厂接口的实现类;

实现

       在《设计模式 - 可复用的面向对象软件》一书中将工厂方法分为参数化工厂方法(简单工厂)非参数化工厂方法(工厂模式)两种。

       下面例子中,以鼠标为例。看看如何使用参数化工厂方法和非参数化工厂方法去实现一个鼠标的生产。

       在以下示例中,开发语言均使用C#,在采用其它语言实现时,会有些许不同。

  • 参数化工厂方法(简单工厂)

       使用简单工厂去实现一个产品的生产,我们首先需要抽象出这个产品的接口,再分别由不同类型的产品去实现这个接口。之后,我们还需要一个承载工厂方法的工厂类来生产这个产品,供调用者使用。

image

public interface IMouse
{
    string GetBrand();
}

public class LogitechMouse : IMouse
{
    public string GetBrand()
    {
        return "罗技-Logitech";
    }
}

public class RazeMouse : IMouse
{
    public string GetBrand()
    {
        return "雷蛇-Raze";
    }
}

public class MouseFactory
{
    public IMouse CreateMouse(string brand)
    {
        if (string.IsNullOrEmpty(brand))
        {
            return null;
        }

        if(brand == "罗技")
        {
            return new LogitechMouse();
        }
        else if (brand == "雷蛇")
        {
            return new RazeMouse();
        }
        else
        {
            return null;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        //创建工厂
        MouseFactory factory = new MouseFactory();

        //通过工厂生产实体
        IMouse mouseA = factory.CreateMouse("罗技");
        IMouse mouseB = factory.CreateMouse("雷蛇");

        Console.WriteLine($"MouseA的品牌是:{mouseA.GetBrand()}");
        Console.WriteLine($"MouseB的品牌是:{mouseB.GetBrand()}");
        Console.ReadKey();
    }
}

       示例中MouseFactory的CreateMouse函数就是所谓的工厂方法,调用者通过MouseFactory的实例调用工厂方法来获取具体的鼠标实体,工厂方法通过调用者的需求(入参)去生产相应类型的鼠标实体。

       不难看出,示例中存在如下缺陷:

    1. 当调用者向工厂下达的需求不能够被工厂识别时,比如传入“罗技鼠标”(对于鼠标生产来讲,罗技与罗技鼠标没有本质区别),工厂将不能正确生产对应的鼠标实体。
    2. 当工厂增加新的鼠标种类时,就需要在MouseFactory的CreateMouse函数中增加if else语句,不符合开闭原则(对拓展开放,对修改关闭)。

       为解决这两个问题,做出如下修改。

/// <summary>
/// 鼠标常数,维护鼠标类的完全限定名
/// </summary>
public class MouseBrandConst
{
    public static readonly string Logitech = typeof(LogitechMouse).FullName;
    public static readonly string Raze = typeof(RazeMouse).FullName;
}

public class MouseFactory
{
    public IMouse CreateMouse(string brand)
    {
        if (string.IsNullOrEmpty(brand))
        {
            return null;
        }

        //if(brand == "罗技")
        //{
        //    return new LogitechMouse();
        //}
        //else if (brand == "雷蛇")
        //{
        //    return new RazeMouse();
        //}
        //else
        //{
        //    return null;
        //}

        //反射鼠标实体
        return Activator.CreateInstance(Type.GetType(brand)) as IMouse;
    }
}

 class Program
 {
     static void Main(string[] args)
     {
         //创建工厂
         MouseFactory factory = new MouseFactory();

         //通过工厂生产实体
         //IMouse mouseA = factory.CreateMouse("罗技");
         //IMouse mouseB = factory.CreateMouse("雷蛇");
         IMouse mouseA = factory.CreateMouse(MouseBrandConst.Logitech);
         IMouse mouseB = factory.CreateMouse(MouseBrandConst.Raze);

         Console.WriteLine($"MouseA的品牌是:{mouseA.GetBrand()}");
         Console.WriteLine($"MouseB的品牌是:{mouseB.GetBrand()}");
         Console.ReadKey();
     }
 }

       增加鼠标常数类,将调用者的需求(入参)规范化,防止调用者向工厂下达不能被工厂识别的需求。在MouseFactory的CreateMouse函数中通过反射来生产鼠标实体。这样,在工厂增加新的鼠标种类时,只需要增加实现了IMouse接口的鼠标类,并将该鼠标类的完全限定名维护在鼠标常数中即可。

       需要说明的是,常数类只是提供给调用者的一个入参模板,并不能完全限定其传入的参数。虽然通过反射可以让我们在增加新的产品时不必修改工厂,但这只适用于所有产品的实例化逻辑一致的情况下,另外反射的效率要比new的效率低。

  • 非参数化工厂方法(工厂模式)

       使用工厂模式去实现一个产品的生产,我们首先需要抽象出这个产品的接口,再分别由不同类型的产品去实现这个接口。之后,我们还需要抽象出一个工厂的接口,再分别由生产不同类型产品的工厂去实现这个接口,供调用者使用。

image

public interface IMouse
{
    string GetBrand();
}

public class LogitechMouse : IMouse
{
    public string GetBrand()
    {
        return "罗技-Logitech";
    }
}

public class RazeMouse : IMouse
{
    public string GetBrand()
    {
        return "雷蛇-Raze";
    }
}

public interface IMouseFactory
{
    IMouse CreateMouse();
}

public class LogitechMouseFactory : IMouseFactory
{
    public IMouse CreateMouse()
    {
        return new LogitechMouse();
    }
}

public class RazeMouseFactory : IMouseFactory
{
    public IMouse CreateMouse()
    {
        return new RazeMouse();
    }
}

class Program
{
    static void Main(string[] args)
    {
        //创建工厂
        IMouseFactory logitechFactory = new LogitechMouseFactory();
        IMouseFactory razeFactory = new RazeMouseFactory();

        //通过工厂生产实体
        IMouse mouseA = logitechFactory.CreateMouse();
        IMouse mouseB = razeFactory.CreateMouse();

        Console.WriteLine($"MouseA的品牌是:{mouseA.GetBrand()}");
        Console.WriteLine($"MouseB的品牌是:{mouseB.GetBrand()}");
        Console.ReadKey();
    }
}

       示例中IMouseFactory的CreateMouse函数就是所谓的工厂方法,调用者通过IMouseFactory的子类去实例化IMouseFactory接口本身(多态,面向对象的基本特征之一),再调用工厂方法去实例化相应鼠标实体。

       这样的好处是,在工厂增加新的鼠标种类时,只需要增加实现了IMouse接口的鼠标类和负责生产这个鼠标的实现了IMouseFactory接口的工厂类(承载了工厂方法的类,如示例中的LogitechMouseFactory )即可。在符合开闭原则的同时避免了简单工厂中不能完全将调用者传入的参数规范化导致的工厂不能正确生产对应的鼠标实体的问题,并且new的效率要比反射效率高。

       虽然在工厂模式中解决了简单工厂的一些弊端,但是随着鼠标种类的增加,对应的工厂数量也会日益庞大。在工厂模式中,这是不可避免的一个问题。

总结

       工厂方法模式,简单的理解就是将一系列对象的实例化逻辑封装到一个或几个从同一接口派生的类中,使调用者更专注于面向接口的开发而不必过于关心具体的实现,给予了我们很大的灵活性。工厂的指定即可以是静态的(编译时指定)也可以是动态的(运行时指定)。它是符合开闭原则的,方便了我们对于程序的扩展及维护。

       但在简单工厂(参数化工厂方法)下,增加了程序对工厂类的依赖,一旦工厂类不能正常使用,会有较大的影响范围。而在工厂模式(非参数化工厂方法)中,虽然减少了程序对某个工厂类的依赖,但随着产品的增加,工厂数量也会日益庞大。

       无论是简单工厂还是工厂模式,在产品的基数少时,使用该模式反而会增加我们的工作量。所以,并不是所有的场景下都适用。

 

       以上,就是我对工厂方法的理解,希望对你有所帮助。

       示例源码:https://gitee.com/wxingChen/DesignPatternsPractice

       系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html

       本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10078547.html)

posted @ 2018-12-06 19:04  王兴Chen  阅读(494)  评论(0编辑  收藏  举报