欢迎来到DIVCSS5查找CSS资料与学习DIV CSS布局技术!
Factory Method(工厂方法)
 
Factory Method(工厂方法)属于创建型模式,利用工厂方法创建对象实例而不是直接用 New 关键字实例化。
 
理解如何写出工厂方法很简单,但理解为什么要用工厂方法就需要动动脑子了。工厂方法看似简单的将 New 替换为一个函数,其实是体现了面向接口编程的思路,它创建的对象其实是一个符合通用接口的通用对象,这个对象的具体实现可以随意替换,以达到通用性目的。
 
意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。
 
举例子
 
如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。
 
换灯泡
 
我自己在家换过灯泡,以前我家里灯坏掉的时候,我看着这个奇形怪状的灯管,心里想,这种灯泡和这个灯座应该是一体的,市场上估计很难买到适配我这个灯座的灯泡了。结果等我把灯泡拧下来,跑到门口的五金店去换的时候,店员随便给了我一个灯泡,我回去随便拧了一下居然就能用了。
 
我买这个灯泡的过程就用到了工厂模式,而正是得益于这种模式,让我可以方便在家门口就买到可以用的灯泡。
 
卡牌对战游戏
 
卡牌对战中,卡牌有一些基本属性,比如攻防、生命值,也符合一些通用约定,比如一回合出击一起等等,那么对于战斗系统来说,应该怎样实例化卡牌呢?如何批量操作卡牌,而不是通用功能也要拿到每个卡牌的实例才能调用?另外每个卡牌有特殊能力,这些特殊能力又应该如何拓展呢?
 
实现任意图形拖拽系统
 
一个可以被交互操作的图形,它可以用鼠标进行拉伸、旋转或者移动,不同图形实现这些操作可能并不相同,要存储的数据也不一样,这些数据应该独立于图形存储,我们的系统如果要对接任意多的图形,具备强大拓展能力,对象关系应该如何设计呢?
 
意图解释
 
在使用工厂方法之前,我们就要创建一个 用于创建对象的接口,这个接口具备通用性,所以我们可以忽略不同的实现来做一些通用的事情。
 
换灯泡的例子来说,我去门口五金店买灯泡,而不是拿到灯泡材料自己 New 一个出来,就是因为五金店这个 “工厂” 提供给我的灯泡符合国家接口标准,而我家里的灯座也符合这个标准,所以灯座不需要知道对接的灯泡是具体哪个实例,什么颜色,什么形状,这些都无所谓,只要灯泡符合国家标准接口,就可以对接上。
 
对卡牌对战的系统来说,所有卡牌都应该实现同一种接口,所以卡牌对战系统拿到的卡牌应该就是简单的 Card 类型,这种类型具备基本的卡片操作交互能力,系统就调用这些能力完成基本流程就好了,如果系统直接实例化具体的卡片,那不同的卡片类型会导致系统难以维护,卡片间操作也无法抽象化。
 
正式这种模式,使得我们可以在卡牌的具体实现上做一些特殊功能,比如修改卡片攻击时效果,修改卡牌销毁时效果。
 
对图形拖拽系统来说,用到了 “连接平行的类层次” 这个特性,所谓连接平行的类层次,就是指一个图形,与其对应的操作类是一个平行抽象类,而一个具体的图形与具体的操作类则是另一个平行关系,系统只要关注最抽象的 “通用图形类” 与 “通用操作类” 即可,操作时,底层可能是某个具体的 “圆类” 与 “圆操作类” 结合使用,具体的类有不同的实现,但都符合同一种接口,因此操作系统才可以把它们一视同仁,统一操作。
 
意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。
 
所以接口是非常重要的,工厂方法第一句话就是 “定义一个用于创建对象的接口”,这个接口就是 Creator,让子类,也就是具体的创建类(ConcreteCreator)决定要实例化哪个类(ConcreteProduct)。
 
所谓使一个类的实例化延迟到其子类,是因为抽象类不知道要实例化哪个具体类,所以实例化动作只能由具体的子类去做,这样绕一圈的好处是,我们可以将任意多对象看作是同一类事物,做统一的处理,比如 无论何种灯泡实例都满足通用的灯座接口,所有工厂实例化的卡牌都具备玩一局卡牌游戏的基本功能,任何图形与交互类都满足特定功能关系,这种思想让生活和设计得到了大幅简化。
 
结构图
 
Creator 就是工厂方法,ConcreteCreator 是实现了 Creator 的具体工厂方法,每一个具体工厂方法生产一个具体的产品 ConcreteProduct,每个具体的产品都实现通用产品的特性 Product。
 
代码例子
 
下面例子使用 typescript 编写。
 
// 产品接口
 
interface Product {
 
  save: () => void;
 
}
 
// 工厂接口
 
interface Creator {
 
  createProduct: () => Product;
 
}
 
// 具体产品
 
class ConcreteProduct implements Product {
 
  save = () => {};
 
}
 
// 具体工厂
 
class ConcreteCreator implements Creator {
 
  createProduct = () => {
 
    return new ConcreteProduct();
 
  };
 
}
 
创建一个 Product 的子类 ConcreteCreator,并返回一个实现了 Product 的具体实例 ConcreteProduct,这样我们就可以方便使用这个工厂了。
 
工厂方法并不是直接调用 new ConcreteCreator().createProduct 那么简单,这样体现不出任何抽象性,真正的场景是,在一个创建产品的流程中,我们只知道拿到的工厂是 Creator:
 
function main(anyCreator: Creator) {
 
  const product = anyCreator.createProduct()
 
}
 
在外面调用 main 函数时,实际传进去的是一个具体工厂,比如 myCreator,但关键是 main 函数不用关心到底是哪一个具体工厂,只要知道是个工厂就行了,具体对象创建过程交给了其子类。
 
你也许也发现了,这就是抽象工厂中其中的一步,所以抽象工厂使用了工厂方法。
 
弊端
 
工厂方法中,每创建一种具体的子类,就要写一个对应的 ConcreteCreate,这相对比较笨重,但有意思的是,如果将创建多个对象放到一个 ConcreteCreate 中,就变成了 简单工厂模式,新增产品要修改已有类不符合开闭模式,反而推荐写成本文说的这种模式。
 
彼之毒药吾之蜜糖,要知道没有一种设计模式解决所有问题,没有一种设计模式没有弊端,而这个弊端不代表这个设计模式不好,一个弊端的出现可能是为了解决另一个痛点。 要接受不完美的存在,这么多种设计模式就是对应了不同的业务场景,为合适的场景选择一种能将优势发扬光大,以至于能掩盖弊端,就算进行了合理的架构设计。

如需转载,请注明文章出处和来源网址:http://www.divcss5.com/html/h63820.shtml