5评论

读书笔记-设计模式-可复用版-简单工厂VS工厂方法

Mitty 2019-03-08 1.7k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏开发行业精英群711501594

在设计模式可复用版中,并没有提到简单工厂,我是在大话设计模式中看到这个说法


实际上,简单工厂在工厂方法(Factory Method)一章节中,叫做参数化的工厂方法,接受一个标识对象种类的参数,使得工厂方法可以创建多种产品。


“工厂“即是用于"生产”产品的,简单工厂是通过一个统一的接口,通常是静态的,接受一个对象种类的参数,通过if else或是switch case 条件判断,返回不同种类的对象。


工厂方法(Factory Method) 概念:


定义一个用于创建对象的接口,让子类决定,实例化哪一个类。Factory Method 使一个类的实例化,延迟到子类。


在大话设计模式中,通过采用计算器,+、-、*、/等不同的运算,来应用工厂方法,也是不错的例子,我这里稍后会采用一个"货币兑换”来进行阐述工厂方法(Factory Method)的使用,但先还原一下运算器的例子吧


public class Operation
  public float num1 { get; set; }
  public float num2 { get; set; }
  public virtual float GetResult() { return 0; }


public class Add : Operation

  public override float GetResult()
  {
    return num1 + num2;
  }

public class Sub : Operation

  public override float GetResult()
  {
    return num1 - num2;
  }
public class Mul : Operation

  public override float GetResult()
  {
    return num1 * num2;
  }
public class Div : Operation

  public override float GetResult()
  {
    if (num2 == 0)
    {
      Debug.LogError("num2 is zero!");
      return 0;
    }
    return num1 / num2;
  }

public class OperationFactory
  public static Operation CreateOperation(string type)
  {
    Operation operation = null;

    switch (type)
    {
      case "+":
        operation = new Add();
        break;
      case "-":
        operation = new Sub();
        break;
      case "*":
        operation = new Mul();
        break;
      case "/":
        operation = new Div();
        break;
    }

    return operation;
  }


测试代码:


Operation operation = OperationFactory.CreateOperation("*");
operation.num1 = 10;
operation.num2 = 10;
Debug.Log(operation.GetResult());


所有的运算符派生成基类Operation(Product) ,并通过OperationFactory参数化工厂方法,根据type,返回指定的运算符对象。


但上面的代码有个弊端,如果我新增一个运算符,我就需要修改一次OperationFactory,

加一个case或是if else,这样也可能会影响其它代码,但你新增一个运算符或是对某一个运算符做修改,影响到其它已经稳定在使用中的运算符,显然是不合理的,这其实违背了开放-封闭原则,设计模式一共有6大原则,这会在后面着重介绍


所以,我们需要解决这种情况,基于接口的的工厂方法可以消除switch case 或if else,从而

新增或是修改运算符,不影响其它部分。


添加一个IOperation接口,接口只提供了一个CreateOperation工厂方法,用于创建运算符:


public interface IOperation{
 Operation CreateOperation();


并分别新建四个运算符类,实现IOperation接口:


public class AddOperation : IOperation
  public Operation CreateOperation()
  {
    return new Add();
  }

public class SubOperation : IOperation
  public Operation CreateOperation()
  {
    return new Sub();
  }

public class MulOperation : IOperation
  public Operation CreateOperation()
  {
    return new Mul();
  }

public class DivOperation : IOperation
  public Operation CreateOperation()
  {
    return new Div();
  }


测试代码:


IOperation operation = new AddOperation();
Operation op = operation.CreateOperation();
op.num1 = 10;
op.num2 = 5;
Debug.Log(op.GetResult());


通过修改,已经消除了switch case / if else带来的弊端,我新增一个运算符,或是修改已有的运算符,对其它运算符都不会有影响,而且也不需要单独的构建一个OperationFactory类,结构要比参数化工厂方法更好


但工厂方法也是有缺点的,比如我新增一个运算后,我就需要新增一个实现IOperation接口的工厂类,用于创建具体的产品(Product),但我个人并不认为这是什么大的缺点,有必要的时候是一定要添加的,但要解决也是有办法的


我们看上面实现了IOperation接口的这些工厂类:


AddOperation

SubOperation

MulOperation

DivOperation


除了名字,所有的实现都是相同的,这里就可以使用泛型或模板,来减少重复的代码,提高代码的重用性


只需要定义一个泛型类:


public class OperationGeneric<T> : IOperation where T : Operation, new()
  public Operation CreateOperation()
  {
    return new T();
  }

测试代码:

IOperation operation = new OperationGeneric<Mul>();
Operation op = operation.CreateOperation();
op.num1 = 10;
op.num2 = 5;
Debug.Log(op.GetResult());



IOperation operation = new OperationGeneric<Mul>();

需要什么运算符,只需要在<>中指定即中。


这样,上面新增的:


AddOperation

SubOperation

MulOperation

DivOperation


就全部可以删除掉了,新增的运算符,也不需要再新增一个实现IOperation的工厂类了。


使用模板,可以避免创建子类。


还有一点需要提到,之前在Singleton章节讲到过,Lazy Initalization 懒汉式或是延迟初始化,只有在使用的时候,如果不存在,我才会去创建


工厂方法,目前就是采用的Lazy Initalization.


再贴下结构图:



Product 产品的基类,我们通常要派生实现,因为尽量的避免面向具体的类(ConcreteClass)编程,要面向抽象编程。


ConcreteProduct 派生自Product


在上面的例子中,Operation是Product,是基类

Add,Sub,Mul,Div分别派生自Operation基类,他们是ConcreteProduct


Creator 是工厂方法的接口

ConcreteCreator是实现了Creator接口的具体工厂类,创建不同运算符的类。


AddOperation,SubOperation...这些(但我们通过泛型来避免创建更多的子类,不要忘记这一点)


在抽象工厂Abstract Factory中,经常用工厂方法Factory Method来实现,下一章,我们会来介绍创建型的最后一个设计模式,抽象工厂Abstract Factory.


最后,提供货币兑换的例子,以巩固工厂方法(Factory Method)的练习 :


假设,我现在有100块钱人民币,我想要兑换成美元,英镑,欧元和卢布,如何通过工厂方法来实现?


这里的美元,英镑,欧元和卢布,就是我们Product,我们先需要定义一个基类Product,这是为了避免向面具体的产品(ConcreteProduct)进行编程,将公共的接口抽象出来。然后创建相应的子类派生实现它。


public class Exchange
  public float amount{get;set;}//现金数
  public virtual float Translation() { return 0; }//返回兑换数

public class USDExchange : Exchange

  public override float Translation()
  {
    return amount * 0.1490f;
  }

public class GBPExchange : Exchange

  public override float Translation()
  {
    return amount * 0.1131f;
  }

public class EURExchange : Exchange

  public override float Translation()
  {
    return amount * 0.1318f;
  }

public class RUBExchange : Exchange

  public override float Translation()
  {
    return amount * 9.8227f;
  }


下面定义工厂方法的接口:


public interface IExchange
  Exchange CreateExchange();

为了避免创建子类,我们使用泛型:

public class ExchangeGeneric<T> : IExchange where T : Exchange, new()
  public Exchange CreateExchange()
  {
    return new T();
  }


测试代码:


IExchange exchangeFactory = new ExchangeGeneric<RUBExchange>();
Exchange exchange = exchangeFactory.CreateExchange();
exchange.amount = 100;
Debug.Log("amount:"+exchange.Translation());


输出结果:

amount:982.27


(我对俄罗斯是有情怀的:)


工厂方法就介绍到这里,接下来会介绍抽象工厂Abstract Factory的使用,然后会对创建型的五种设计模式,做一次总结。


就到这里,周末快到了,周末愉快!


感谢您的阅读, 如文中有误,欢迎指正,共同提高 


欢迎关注我的技术分享的微信公众号,Paddtoning帕丁顿熊,期待和您的交流


本文作者

Mitty

欢迎关注我的技术分享微信公众号:PaddingtonCoder (Paddington帕丁顿熊,很喜欢这个名字,第一次出国就是英国,很意外的机会,了解了一点英国的历史,知道了帕丁顿熊,看了帕丁顿熊的电影,来到了伦敦的帕丁顿地铁站,随处可见肥肥的鸽子总是抢镜......很有趣儿)

腾讯游戏学院公众号