[Python设计模式] 第8章 学习雷锋好榜样——工厂方法模式

github地址:https://github.com/cheesezh/python_design_patterns

简单工厂模式 v.s. 工厂方法模式

以简单计算器为例,对比一下简单工厂模式和工厂方法模式的区别。

简单工厂模式

from abc import ABCMeta, abstractmethod


class Operation():
    """
    抽象产品类(运算符类)
    """
    __metaclass__ = ABCMeta

    def __init__(self):
        self.result = None

    @abstractmethod
    def get_result(self):
        pass

    
class AddOperation(Operation):
    """
    具体产品类(加法运算符)
    """
    def get_result(self, number_a, number_b):
        self.result = number_a + number_b
        return self.result

    
class SubOperation(Operation):
    """
    具体产品类(减法运算符)
    """
    def get_result(self, number_a, number_b):
        self.result = number_a - number_b
        return self.result

    
class MulOperation(Operation):
    """
    具体产品类(乘法运算符)
    """
    def get_result(self, number_a, number_b):
        self.result = number_a * number_b
        return self.result
    
    
class DivOperation(Operation):
    """
    具体产品类(除法运算符)
    """
    def get_result(self, number_a, number_b):
        if number_b == 0:
            print("With operator '/', the second number can not be zero.")
            return self.result
        self.result = number_a / number_b
        return self.result
    
    
class OperationFactory():
    """
    产品工厂类
    """
    @classmethod
    def create_operate(self, operator):
        oper = None
        if operator == "+":
            oper = AddOperation()
        elif operator == "-":
            oper = SubOperation()
        elif operator == "*":
            oper = MulOperation()
        elif operator == "/":
            oper = DivOperation()
        else:
            print("Wrong operator.")
        return oper

number_a = int(input("input a number:"))
operator = str(input("input a operater(+ - * /):"))
number_b = int(input("input a number:"))

oper = OperationFactory.create_operate(operator)
print(oper.get_result(number_a, number_b))
input a number:99
input a operater(+ - * /):/
input a number:9
11.0

工厂方法模式

from abc import ABCMeta, abstractmethod


class IFactory():
    """
    通用工厂接口
    """
    __metaclass__ = ABCMeta
    
    @abstractmethod
    def create_operation(self):
        pass

    
class AddFactory(IFactory):
    """
    实现工厂接口的加法工厂类
    """
    def create_operation(self):
        return AddOperation()

    
class SubFactory(IFactory):
    """
    实现工厂接口的剑法工厂类
    """
    def create_operation(self):
        return SubOperation()

    
class MulFactory(IFactory):
    """
    实现工厂接口的乘法工厂类
    """
    def create_operation(self):
        return MulOperation()
    

class DivFactory(IFactory):
    """
    实现工厂接口的除法工厂类
    """
    def create_operation(self):
        return DivOperation()
    

def main():
    number_a = int(input("input a number:"))
    operator = str(input("input a operater(+ - * /):"))
    number_b = int(input("input a number:"))

    if operator == "+":
        oper_factory = AddFactory()
    elif operator == "-":
        oper_factory = SubFactory()
    elif operator == "*":
        oper_factory = MulFactory()
    elif operator == "/":
        oper_factory = DivFactory()
    else:
        print("Wrong operator.")

    oper = oper_factory.create_operation()
    print(oper.get_result(number_a, number_b))
    
main()
input a number:99
input a operater(+ - * /):/
input a number:11
9.0

点评

工厂方法更复杂了?

如果需要增加其他运算,比如求M的N次方。

在简单工厂模式里,先增加一个求M的N次方的产品类,然后更改工厂类的if判断增加分支即可。

在工厂方法模式里,先增加一个求M的N次方的产品类,还要新增一个相关工厂类,最后还有修改客户端代码。

这就是简单工厂和工厂方法的区别所在。简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关类,对于客户端来说,去除了与具体产品的依赖。但是,如果要增加一个新的功能,比如求M的N次方,需要更改工厂类的if判断分支条件,修改原有的类?违背了开放-封闭原则,这可不是好方法。所以就需要工厂方法模式来处理。

工厂方法模式

工厂方法模式,定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类[DP]。

相当于将简单工厂中的工厂类,变成了一个工厂抽象接口和多个具体生成对象的工厂,于是我们要增加求M的N次方的功能,就不需要更改工厂类,只需要增加此功能的运算类和相应的工厂类即可。这样整个工厂和产品体系其实都没有修改,而只是扩展,这就完全符合了开放-封闭原则。

但是,工厂方法模式是现实,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。要增加新功能,本来修改工厂类,现在修改客户端了。

题目

木叶学校组织学雷锋活动,让鸣人,小樱,佐助帮敬老院的老人扫地,洗衣,买米,如何实现?

class LeiFeng():
    
    def sweep(self):
        print("扫地")
    
    def wash(self):
        print("洗衣")
    
    def buy_rice(self):
        print("买米")
        
class Student(LeiFeng):
    pass

def main():
    mingren = Student()
    xiaoying = Student()
    zuozhu = Student()
    
    mingren.sweep()
    xiaoying.wash()
    zuozhu.buy_rice()
main()
扫地
洗衣
买米

点评

  • 学生都会毕业,但是帮助老人是长期工作,所以每次不同的人帮助老人,都需要改客户端代码,而且老人不可能知道所有来帮忙的学生的名字;
  • 除了学生,社区志愿者也可以帮助老人

如何用简单工厂方法解决上述问题?

class Volunteer(LeiFeng):
    pass


class SimpleFactory():
    
    @classmethod
    def create_leifeng(self, leifeng_type):
        self.leifeng = None
        if leifeng_type == "学生":
            self.leifeng = Student()
        elif leifeng_type == "志愿者":
            self.leifeng = Volunteer()
        else:
            print("ERROR LeiFeng Type")
        return self.leifeng
    
def main():
    studentA = SimpleFactory.create_leifeng("学生")
    studentA.buy_rice()
    studentB = SimpleFactory.create_leifeng("学生")
    studentB.wash()
    studentC = SimpleFactory.create_leifeng("学生")
    studentB.sweep()
    
main()
买米
洗衣
扫地

点评

  • 好的地方,客户端的代码,如果要换志愿者,只需要换参数即可;
  • 坏的地方,在任何实例化的时候都需要写一句SimpleFactory.create_leifeng("学生"),这会导致大量重复,在修改为志愿者的时候非常麻烦,可以用工厂方法解决这个问题;
from abc import ABCMeta, abstractmethod


class ILeiFengFactory():
    __metaclass__ = ABCMeta
    
    @abstractmethod
    def create_leifeng(self):
        pass

    
class StudentFactory(ILeiFengFactory):
    
    def create_leifeng(self):
        return Student()

    
class VolunteerFactory(ILeiFengFactory):
    
    def create_leifeng(self):
        return Volunteer()

def main():
    leifeng_factory = StudentFactory()
    stu1 = leifeng_factory.create_leifeng()
    stu2 = leifeng_factory.create_leifeng()
    stu3 = leifeng_factory.create_leifeng()
    
    stu1.sweep()
    stu2.wash()
    stu3.buy_rice()
    
main()
扫地
洗衣
买米

点评

  • 此时如果要将学生改成志愿者,只需要修改一行代码即可;
  • 工厂方法克服了简单工厂违背开放-封闭原则的缺点,又保持了封装对象创建过程的优点;

总结

简单工厂和工厂方法都是集中封装了对象的创建,使得要更换对象时,不需要做大的改动就可以实现,降低了客户程序和产品对象的耦合。

工厂方法是简单工厂模式的进一步抽象和推广,由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。但缺点是由于每加一个产品,就需要加一个产品工厂类,增加了额外开发量。

另外,工厂方法还是没有避免修改客户端的代码,可以利用反射解决避免分支判断的问题。

posted @ 2018-07-30 21:37  ZH奶酪  阅读(403)  评论(0编辑  收藏  举报