工厂方法模式(Python设计模式二)


在面向对象编程中,术语“工厂”表示一个负责创建其他类型对象的类。通常情况下,作为一个工厂的类有一个对象以及与它关联的多个方法。客户端使用某些参数调用此方法,之后工厂会据此创建所需类型的对象,然后将它们返回给客户端。

  • 简单工厂模式:允许接口创建对象,但不会暴露对象的创建逻辑。
  • 工厂方法模式:允许接口创建对象,但使用哪个类来创建对象,则是交由子类决定的。
  • 抽象工厂模式:抽象工厂是一个能够创建一系列相关的对象而无需指定/公开其具体类的接口。该模式能够提供其他工厂的对象,在其内部创建其他对象。

简单工厂模式

简单工厂类似个体户,生产产品的种类和能够满足的需求相对简单。产品通常只有一个抽象基类,这个基类至少有一个抽象方法,通过继承这个抽象类得到具体的产品。此时工厂要做的就是根据客户需求,实例化具体产品来满足需求。

从下面的UML图可以看出客户端使用Factory类,该类具有create_type()方法。当客户端使用类型参数调用create_type()方法时,Factory会根据传入的参数,返回Product1或Product2。接下来通过一个简单的例子来理解。

实现简单工厂方法

我们将创建一个名为Animal的抽象产品。Animal是一个抽象的基类,它带有方法do_say()。我们利用Animal接口创建了两种产品(Cat和Dog),并实现了do_say()方法来提供这些动物的相应的叫声。ForestFactory是一个带有make_sound()方法的工厂。根据客户端传递的参数类型,它就可以在运行时创建适当的Animal实例,并输出正确的声音

class Animal(metaclass=ABCMeta):
    @abstractmethod
    def do_say(self):
        pass

class Dog(Animal):
    def do_say(self):
        print("Wow Wow!")

class Cat(Animal):
    def do_say(self):
        print("Meow Meow")

class ForestFactory(object):
    def make_sound(self, an):
        return eval(an).do_say()


if __name__ == '__main__':
    ff = ForestFactory()
    animal = input("Which animal should make_sound Dog or Cat?")
    ff.make_sound(animal)

说了半天,这个简单工厂类不就是面向对象的继承和多态嘛。确实就是这么回事,从不同的角度来看待相同的概念而已。这里可以看到客户端不直接实例化具体产品。如果新增需求,只需要重新生成具象的产品就可以了,也就起到了解耦合的作用。

工厂方法模式

上面简单工厂模式确实太简单了,工厂模式在此基础上丰富了“工厂的生产能力”,与简单工厂模式相比有以下几点不同:

  1. 定义工厂基类,创建具体产品的任务放在子类完成,也就是工厂类是通过继承的方式而不是实例化来完成
  2. 工厂方法使设计更加具有可定制性。它可以返回相同的实例或子类,而不是某种类型的对象

实现工厂方法

让我们拿一个现实世界的场景来理解工厂方法的实现。假设我们想在不同类型的社交网络(例如LinkedIn、Facebook等)上为个人或公司建立简介。那么,每个简介都有某些特定的组成章节。在LinkedIn的简介中,有一个章节是关于个人申请的专利或出版作品的。在Facebook上,你将在相册中看到最近度假地点的照片区。此外,在这两个简介中,都有一个个人信息的区。因此,简而言之,我们要通过将正确的区添加到相应的简介中来创建不同类型的简介。

下面让我们来看看具体如何实现。在下面的代码示例中,首先定义接口Product。我们将创建一个Section抽象类来定义一个区是关于哪方面内容的,让它尽量保持简单,同时还提供一个抽象方法describe()

from abc import abstractmethod


class Section:
    @abstractmethod
    def describe(self):
        pass


class PersonalSection(Section):
    def describe(self):
        print("Personal Section")


class AlbumSection(Section):
    def describe(self):
        print("Album Section")


class PatentSection(Section):
    def describe(self):
        print("Patent Section")


class PublicSection(Section):
    def describe(self):
        print("Public Section")

我们创建了一个名为Profile的抽象类Creator。Profile [Creator]抽象类提供了一个工厂方法,即createProfile()。该方法应该由ConcreteClass实现,来实际创建带有适当区的简介。Profile抽象类不知道每个简介应具有哪些区。例如,Facebook的简介应该提供个人信息区和相册区。所以我们将让子类来决定这些事情。我们创建了两个ConcreteCreator类,即Linkedin和Facebook。每个类都实现createProfile()抽象方法,由该方法在运行时实际创建(实例化)多个区(ConcreteProducts):


class Profile: def __init__(self): self.sections = [] self.create_profile() @abstractmethod def create_profile(self): pass def get_sections(self): return self.sections def add_section(self, section): self.sections.append(section) class Linkedin(Profile): def create_profile(self): self.add_section(PersonalSection()) self.add_section(PatentSection()) self.add_section(PublicSection()) class Facebook(Profile): def create_profile(self): self.add_section(PersonalSection()) self.add_section(AlbumSection()) if __name__ == '__main__': profile_type = input("Which Profile you'd like to create?[Linkedin or Facebook]") profile = eval(profile_type)() print("creating Profile..", type(profile).__name__) print("Profile has sections --", profile.get_sections()) # Which Profile you'd like to create?[Linkedin or Facebook]Facebook # creating Profile.. Facebook # Profile has sections -- [<__main__.PersonalSection object at # 0x7ff09ac4e4d0>, <__main__.AlbumSection object at 0x7ff09ac4e510>]

抽象工厂模式

抽象工厂模式的主要目的是提供一个接口来创建一系列相关对象,而无需指定具体的类。工厂方法将创建实例的任务委托给了子类,而抽象工厂方法的目标是创建一系列相关对象。

实际上,抽象工厂模式不仅确保客户端与对象的创建相互隔离,同时还确保客户端能够使用创建的对象。但是,客户端只能通过接口访问对象。如果要使用一个系列中的多个产品,那么抽象工厂模式能够帮助客户端一次使用来自一个产品/系列的多个对象。例如,如果正在开发的应用应该是平台无关的,则它需要对各种依赖项进行抽象处理,这些依赖项包括操作系统、文件系统调用,等等。抽象工厂模式负责为整个平台创建所需的服务,这样的话,客户端就不必直接创建平台对象了。

实现抽象工厂方法

设想一下你最喜欢的披萨饼店的情况。它提供多种披萨饼,对吧?等等,我知道你想立即订购一份,现在让我们讨论这个场景吧!
现在想象一下,我们开办了一家披萨店,供应美味的印式和美式披萨饼。为此我们首先创建一个抽象基类——PizzaFactoryPizzaFactory类有两个抽象方法即createVegPizza()createNonVegPizza(),它们需要通过ConcreteFactory实现。在这个例子中,我们创造了两个具体的工厂,分别名为IndianPizzaFactoryUSPizzaFactory。下面让我们看看这两个具体工厂的实现代码:

from abc import ABCMeta, abstractmethod


class PizzaFactory(metaclass=ABCMeta):
    @abstractmethod
    def create_veg_pizza(self):
        pass

    @abstractmethod
    def create_non_veg_pizza(self):
        pass


class IndianPizzaFactory(PizzaFactory):
    def create_veg_pizza(self):
        return DeluxVeggiePizza()

    def create_non_veg_pizza(self):
        return CheckPizza()


class USPizzaFactory(PizzaFactory):
    def create_veg_pizza(self):
        return MexicanVegPizza()

    def create_non_veg_pizza(self):
        return HamPizza()

接下来创建抽象产品VegPizzaNonVegPizza。这里的想法是,素食披萨饼配有适当的外皮、蔬菜和调味料,非素食披萨饼在素食披萨饼上面搭配非素食食材。

class NonVegPizza(metaclass=ABCMeta):
    @abstractmethod
    def serve(self, veg_pizza: VegPizza):
        pass


class DeluxVeggiePizza(VegPizza):
    def prepare(self, ):
        print("Prepare ", type(self).__name__)


class CheckPizza(NonVegPizza):
    def serve(self, veg_pizza: VegPizza):
        print(type(self).__name__, "is served with Chicken on ", type(veg_pizza).__name__)


class MexicanVegPizza(VegPizza):
    def prepare(self):
        print("Prepare ", type(self).__name__)


class HamPizza(NonVegPizza):
    def serve(self, veg_pizza):
        print(type(self).__name__, "is served with Ham on ", type(veg_pizza).__name__)

最终用户来到PizzaStore并要一份美式非素食披萨的时候,USPizzaFactory负责准备素食,然后在上面加上火腿,马上就变成非素食披萨了!

class PizzaStore:
    def make_pizzas(self):
        for factory in [IndianPizzaFactory(), USPizzaFactory()]:
            veg_pizza = factory.create_veg_pizza()
            non_veg_pizza = factory.create_non_veg_pizza()

            veg_pizza.prepare()
            non_veg_pizza.serve(veg_pizza)


if __name__ == '__main__':
    pizza = PizzaStore()
    pizza.make_pizzas()

# Prepare  DeluxVeggiePizza
# CheckPizza is served with Chicken on  DeluxVeggiePizza
# Prepare  MexicanVegPizza
# HamPizza is served with Ham on  MexicanVegPizza

总结

工厂设计模式使用非常多,稍大一点的项目都会涉及到抽象工厂类。设计抽象工厂基类非常重要,需要根据实际业务把产品类和工厂类区分开来,然后设计工厂方法。好的设计可以让代码的开发和维护变得清晰容易。设计模式作为开发的软实力,需要在实际过程中不断思考和总结,最终灵活应用。

标签:

发表评论

邮箱地址不会被公开。