在 设计模式之工厂模式 中,我们提到过工厂模式仔细区分的话分为三种,分别是简单工厂模式、工厂方法模式以及抽象工厂模式。在上面已经介绍完毕了工厂模式(也就是简单工厂模式)。本篇文章主要学习的是工厂模式的第二种,工厂方法模式。
注:本文是基于 设计模式之工厂模式 的文章之上进行编码以及项目说明
首先回顾下工厂模式的基本概念:我们在创建对象时不会对客户端直接暴露创建逻辑,而是 通过使用一个共同的接口根据不同的条件来指向具体想要创建的对象。
通过一个共同的接口根据不同的条件来指向想要创建的对象,这个切入点肯定没错,在上一篇文章也讲到,我们通过使用工厂模式的优点在于一个调用者想创建一个对象,只要知道其名称(也就是不同的标签)就可以在工厂获取具体的对象;其次,在这种设计模式下会屏蔽产品的具体实现,调用者只关心产品的接口、无需关心内部实现。
但是,这样也会带来一个问题,如果我想增加一个(也就是具体的对象),那就需要扩展工厂类(也就是首先要增加不同的标签,然后增加不同标签所对应的对象)。如果这样操作的话,就违背了“开闭”原则。那么,什么是开闭原则?
在 设计模式概念与简介 这篇文章中简单介绍了开闭原则,那么这里对开闭原则做更加详细的分析。
开闭原则(OCP)是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。开、闭的字面理解就是对于扩展是开放的,对于修改是关闭的,这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。更通俗一点,也就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能(Functions)等等,应该在不修改现有代码的基础上,引入新功能。
既然开闭原则是如此的重要,那么开发者应该如何实现开闭原则?
A:抽象约束
抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:
通过接口或抽象类约束扩散,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法。
参数类型,引用对象尽量使用接口或抽象类,而不是实现类,这主要是实现里氏替换原则的一个要求
抽象层尽量保持稳定,一旦确定就不要修改
B:元数据(metadata)控件模块行为
编程是一个很苦很累的活,那怎么才能减轻压力呢?答案是尽量使用元数据来控制程序的行为,减少重复开发。什么是元数据?用来描述环境和数据的数据,通俗的说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。
C:制定项目章程
在一个团队中,建立项目章程是非常重要的,因为章程是所有人员都必须遵守的约定,对项目来说,约定优于配置。这比通过接口或抽象类进行约束效率更高,而扩展性一点也没有减少。
D:封装变化
对变化封装包含两层含义:
(1)将相同的变化封装到一个接口或抽象类中
(2)将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。
封装变化,也就是受保护的变化,找出预计有变化或不稳定的点,我们为这些变化点创建稳定的接口。
开闭原则的具体实现与相互关系:
开闭原则被称作面向对象设计的终极目标。因此,针对开闭原则的实现方法,一直都有面向对象设计的大师费尽心机去研究开闭原则的实现方式。但是在平时的开发中我们已经直接或者间接的已经接触到了开闭原则的设计理念。比如,设计模式概念中提到的里氏代换原则、依赖倒转原则、接口隔离原则 以及常用的接口、抽象类等等,都可以看作是开闭原则的具体实现方法。
那么在回到上面的问题,当需求发生变化的时候,简单工厂模式下进行修改操作的话是违背开闭原则的(当然你说这样也无所谓,这个功能怎么实现我不管、反正可以用就行),那么如何解决上面的问题?
比较高效和容易理解的方式是,在定义一个抽象的英雄工厂类型的基类,让子类自己去实现该基类(也就是工厂与实例对象互相对应)。
首先还是跟上一篇文章一样,基本代码不变:
接着是已持有的三个英雄:
上面的代码和简单工厂模式下的代码都是一样的,针对上面的问题,我们提到可以定义一个抽象的英雄工厂类型的基类
其中,这里基类的抽象方法,返回的是具体的英雄参数类型,那么通过子类就可以进行具体的操作(将对象返回出去即可)三个具体的英雄对应三种具体的工厂
定义完三个工厂以后,我们就可以测试使用了:
可能会说,这两种模式有什么区别?这里先上一下两种工厂模式下的测试类比较:
其中,红色箭头的是简单工厂模式(这种模式下将所有的标签和对象全部统一在这里进行管理使用)
蓝色箭头代表的是工厂方法模式,在这种模式下,各个工厂内对象依据工厂类型不同进行创建。
简单工厂模式和工厂方法模式的比较:
简单工厂模式是专门定义一个类(工厂类)来负责创建其他类的实例,被创建的实例通常都具有共同的父类。它又称为静态工厂方法模式。本质是由一个工厂类根据传入的参数动态决定创建那一个产品类(这些产品类继承自一个父类或接口)的实例对象。在这种模式下工厂类是整个模式的关键,它包含必要的逻辑判断能够根据外界给定的信息决定究竟应该创建那个具体类的对象。
工厂方法模式是粒度很小的设计模式,因为模式的表现是一个抽象的方法。提前定义用于创建对象的接口让子类决定实例化具体的某一个类,即在工厂和产品中间增加一个接口(或者抽象类),工厂不再负责产品的创建,由接口针对不同条件返回具体的实例,由具体类实例去实现。工厂方法模式是简单工厂模式的衍生解决了之前简单工厂模式下带来的问题。完全实现了开闭原则。工厂方法模式是对简单工厂模式进行了抽象。有一个抽象的Factory类(接口或者抽象)这个类不在负责具体的产品生产(也就是new 对象),而是只指定规范,具体的实现由子类完成。在这种模式下,工厂类和产品往往可以互相对应。即一个抽象工厂对应一个抽象产品,一个具体的工厂对应一个具体的产品,这个具体的工厂就负责生产对象的产品(也就是上面的蓝色箭头)
简单工厂模式和工厂方法模式的优缺点:
简单工厂模式:
优点:工厂类含有必要的逻辑判断,可以依据不同的标签创建不同的对象,实现了对责任的分割,耦合性较低。有利于整个软件体系结构的优化
缺点:拓展性相对较低(因此出现了工厂方法模式)
工厂方法模式:
优点:这种模式是为了克服简单工厂模式下的缺点(主要是开闭原则),这种模式下每个具体的工厂类只完成单一任务,代码简洁,拓展性强
缺点:不易于维护,修改产品对象也要修改对应的工厂类(修改多个产品就要修改多个工厂)
简单工厂模式和工厂方法模式的适用范围:
简单工厂模式:工厂类负责创建的对象比较少,客户只知道传入了工厂类的参数,对于创建的逻辑不关心。且使用一个共同的接口根据不同的条件来指向具体想要创建的对象可以使用简单工厂模式
工厂方法模式:当一个类不知道它所必须创建对象的类或者一个类希望由子类来指定它所创建的对象时;当类将创建的需的职责委托给帮助类中的某一个,并且希望得到指定帮助类的信息,可以使用工厂方法模式
参考资料:百度百科
如果这篇文章对你有帮助,希望各位看官留下宝贵的star,谢谢。
Ps:著作权归作者所有,转载请注明作者, 商业转载请联系作者获得授权,非商业转载请注明出处(开头或结尾请添加转载出处,添加原文url地址),文章请勿滥用,也希望大家尊重笔者的劳动成果。