怎么样让你写的程序分工又分家?
导语:这次是开发者蓝图系列的第二篇,让我们好好聊聊工厂方法模式的前世今生。
之前猿哥就说了,设计模式是源于万千程序猿的真实项目的开发经验,并超脱这些经验,给出的解决方案。
工厂方法模式当然也不例外,它和之前讲过的单例模式一样,也是为了解决创建对象时所出现的问题,有兴趣的小伙伴不妨去去瞅瞅之前那篇单例模式,谁让它是面试题的常客呢。
什么是工厂方法模式
工厂方法模式又叫做虚拟构造函数,属于创建型设计模式,不知道设计模式分类的小伙伴快去看看猿哥下面这篇文章。
那话说回来,到底什么是工厂方法模式呢,它的正经定义是用一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。
猿哥就问一句,懵不懵!什么产品,什么工厂,这都哪跟哪啊!
没错,上面就是我第一次看到这个定义的赶脚,是真滴看不懂。
但别着急,猿哥给你们说一下工厂方法模式的形象版描述。
在工厂方法模式中,创建对象的类就如同一个加工创造实例对象的工厂,而这些实例对象就是被生产出来的一个个产品。
如果创建的产品不多,只要一个工厂类就可以完成生产,那这种模式就变成了“简单工厂模式”。
但它不属于 GoF 提出的 23 种经典设计模式,这里也就不具体展开了,后面谈代码实现的时候还得说它。
当所生产的产品种类繁多时,一个工厂类当然满足不了需求了,但完全创建一个新的厂子,重复性劳动又太多。
所以在简单工厂模式的基础上进一步抽象和推广,并利用继承和多态的特性,由父工厂类所提供的创建方法和接口,由子工厂类去决定具体生产哪种产品。
一个工厂类对应一个产品类,这就是我们今天要聊的工厂方法模式。
工厂方法模式解决的问题
都0202年了,谁还能否认社会分工的细化所带来工作效率的提升,在开发中,让代码们“各司其职”,一直也都是我们追求的目标。
而工厂方法模式所解决的问题,在我看来就一条,就是将产品对象的生产和使用分离,让产品对象的增删改变的更方便!
使用产品的人是不会在意产品是如何产生的,这样将生产和使用分离,不仅避免了直接实例化导致的高耦合,更摆脱了产品增删改带来的工厂逻辑的冗杂。
猿哥我知道,说这么多干巴巴的知识,很容易乏味,如果实在看不下去的小伙伴一定要瞅瞅最后的代码实现,有个印象比什么都强~
工厂方法模式的适用场景
无论是日常开发还是框架源码,工厂方法模式真的随处可见,所以它往往被我们忽视。
按工厂方法模式的特点和所解决的问题,猿哥将工厂方法模式的适用场景分为了三类。
猿哥觉得只要是写代码用过别家写的库或者框架的,没谁不熟悉工厂方法模式,只是灯下黑罢了。
用别人造的轮子时,是不是都需要先创建一个实例化对象,再对这个实例化对象进行自己的需求设计。
其实创建这个实例化对象的工厂类,往往还有一个功能不那么具体的父类,它才是原轮子中的标准组件,没错,继承可能是拓展轮子最简单的方法。
看过源码的小伙伴应该知道,把框架中各个组件的构造代码专门集中到一个父工厂类,定义了一系列的接口规则,而子类工厂不仅继承了这个父类,更允许使用者进行重写。
你看,当你想扩展轮子时,不需要你抽丝剥茧的去一步步寻找哪里有耦合,直接继承重写父工厂类它不香嘛。
甚至对于一些使用者而言,无需关注它背后的实现逻辑,只要知道子类工厂的名字就行,这就是我们接下来要聊的下一个适用场景。
工厂方法模式所做到的将生产和业务代码分离,可以在不影响其他代码的前提下扩展新的产品,为你带来的便利可想而知。
如果说上面那种场景是针对轮子制造者而言,那么这个场景就是针对使用者而言。
无需知道它们背后的逻辑,无需知道具体父工厂类名,只需要了解对应的工厂名即可。
这一点和之前讲过的单例模式有点像,对于像数据库连接这样的资源密集型对象,都起到了节省内存资源的作用。
但和单例模式不同的是,有时我们既需要能够复用现有对象,又需要创建新对象,而且这些对象还需要共用一个接口。
仔细想想这不是就是工厂方法模式嘛,无非是在工厂类中加一个逻辑判断的事。
如果说上面说的还不够具体,那猿哥再给你两个具体场景:
在设计数据库访问时,当你也不知道用户会采用哪一类数据库,以及数据库可能有变化需要重写时,在设计之初就可以使用工厂方法模式。
在设计一个连接服务器的框架时,需要多个协议,可以把这些协议作为产品类,实现统一的接口。
工厂方法模式的优缺点
其实优缺点就是对上面这些理论的总结,看不懂的小伙伴再品品上面的内容就行。
一个调用者想创建一个对象,只需要工厂名称即可。封装了产品的具体实现,调用者只用关注产品接口。
符合开闭原则,这也是它和简单工厂模式最大的不同。扩展性高,如果想增加一个产品,只需要额外扩展一个工厂即可,不需要对原工厂进行任何变动。
避免创建和使用的两部分代码的紧密耦合,使得代码更易维护。
工厂方法模式最大的问题是每次增加一个产品,就需要增加一个具体产品类和一个对应的具体工厂类。
这让整个程序中类的个数成倍增加,毫无疑问的增加系统的复杂度,新类的编译和运行还会给系统带来了额外的资源开销。
工厂方法模式的实现
这里的代码示例的思路源于程杰的《大话设计模式》,我们直接对比一下简单工厂模式和工厂方法模式的区别。
以简单的四则运算的计算器为例,先看看它的 UML 图。
通过代码去实现一下。
from abc import ABCMeta, abstractmethod
class Operation():
"""
抽象产品类(运算符类)
"""
__metaclass__ = ABCMeta
def __init__(self):
self.result = None
@abstractmethod
def GetResult(self):
pass
class AddOperation(Operation):
"""
具体产品类(加法运算符)
"""
def GetResult(self, number1, number2):
self.result = number1 + number2
return self.result
class SubOperation(Operation):
"""
具体产品类(减法运算符)
"""
def GetResult(self, number1, number2):
self.result = number1 - number2
return self.result
class MulOperation(Operation):
"""
具体产品类(乘法运算符)
"""
def GetResult(self, number1, number2):
self.result = number1 * number2
return self.result
class DivOperation(Operation):
"""
具体产品类(除法运算符)
"""
def GetResult(self, number1, number2):
if number2 == 0:
print("分母不能为0")
return self.result
self.result = number1 / number2
return self.result
class OperationFactory():
"""
简单工厂类
"""
@classmethod
def CreateOperate(self, operator):
oper = None
if operator == "+":
oper = AddOperation()
elif operator == "-":
oper = SubOperation()
elif operator == "*":
oper = MulOperation()
elif operator == "/":
oper = DivOperation()
else:
print("运算符输入错误")
return oper
number1 = int(input("请输入数字:"))
operator = str(input("请输入运算符(+ - * /):"))
number2 = int(input("请输入数字:"))
oper = OperationFactory.CreateOperate(operator)
print("运算结果为:%.2f" % oper.GetResult(number1, number2))
接着我们用工厂方法模式实现四则运算,来瞅瞅 UML 图。
工厂方法模式就是一个工厂生产一类产品,借助代码去实现一下上述逻辑。
from abc import ABCMeta, abstractmethod
class Operation():
"""
抽象产品类(运算符类)
"""
__metaclass__ = ABCMeta
def __init__(self):
self.result = None
@abstractmethod
def GetResult(self):
pass
class AddOperation(Operation):
"""
具体产品类(加法运算符)
"""
def GetResult(self, number1, number2):
self.result = number1 + number2
return self.result
class SubOperation(Operation):
"""
具体产品类(减法运算符)
"""
def GetResult(self, number1, number2):
self.result = number1 - number2
return self.result
class MulOperation(Operation):
"""
具体产品类(乘法运算符)
"""
def GetResult(self, number1, number2):
self.result = number1 * number2
return self.result
class DivOperation(Operation):
"""
具体产品类(除法运算符)
"""
def GetResult(self, number1, number2):
if number2 == 0:
print("分母不能为0")
return self.result
self.result = number1 / number2
return self.result
class Factory():
"""
通用工厂接口
"""
__metaclass__ = ABCMeta
@abstractmethod
def CreateOperation(self):
pass
class AddFactory(Factory):
"""
具体工厂类(加法工厂类)
"""
def CreateOperation(self):
return AddOperation()
class SubFactory(Factory):
"""
具体工厂类(减法工厂类)
"""
def CreateOperation(self):
return SubOperation()
class MulFactory(Factory):
"""
具体工厂类(乘法工厂类)
"""
def CreateOperation(self):
return MulOperation()
class DivFactory(Factory):
"""
具体工厂类(除法工厂类)
"""
def CreateOperation(self):
return DivOperation()
def main():
number1 = int(input("请输入数字:"))
operator = str(input("请输入运算符(+ - * /):"))
number2 = int(input("请输入数字:"))
if operator == "+":
operFactory = AddFactory()
elif operator == "-":
operFactory = SubFactory()
elif operator == "*":
operFactory = MulFactory()
elif operator == "/":
operFactory = DivFactory()
else:
print("运算符输入错误")
oper = operFactory.CreateOperation()
print("运算结果为:%.2f" % oper.GetResult(number1, number2))
main()
对比两种设计模式,是不是工厂方法模式反而更复杂了。
如果简单工厂模式中添加一个新产品类,更改工厂类中的判断语句即可。
而工厂方法模式中添加一个新产品类,就还需要新增一个对应的工厂类,最后还需要修改客户端代码。
这就是两者最大的区别。
简单工厂模式中只有一个工厂类,最大的优势可能就是工厂类中包含有逻辑判断,根据客户端的选择创建相应的产品.
但增加一个新产品时,就需要大动干戈的去修改原有的工厂类,这就违反了开闭原则。
两者共有的问题就在于都需要修改客户端的代码,理论上可以通过反射解决避免分支判断的问题,这个我们之后再讲。
总结
工厂方法模式作为一种创建型模式,在任何需要生成复杂对象的地方,都可以用工厂方法模式。
但对于简单的对象,使用工厂方法模式只会徒增整个系统的复杂度,得不偿失。
还是那句话,任何设计模式的使用,都离不开具体需求具体分析。
举个例子,如果一段程序的工厂类和业务类(产品类)由两个小伙伴分开写,如果不用工厂方法模式,光沟通接口就得花上大量的时间,何苦呢!
不知不觉又到星期五了,这个星期小伙伴们有没有努力啊。猿哥给小伙伴们准备了一份 Python 入门到进阶的知识大纲,过段时间就发给你们,敬请期待哟。
看到最后的小伙伴如果认可我的内容,别忘了给猿哥点个【在看】,期待你与我的下一次相逢~