工厂设计模式

时间:2014-01-09 16:35:19

标签: python design-patterns

我正在尝试实施工厂设计模式,并且到现在为止已经做到了这一点。

import abc

class Button(object):
    __metaclass__ = abc.ABCMeta

    html = ""
    def get_html(self, html):
        return self.html

class ButtonFactory():
    def create_button(self, type):
        baseclass = Button()
        targetclass = type.baseclass.capitalize()
        return targetclass

button_obj = ButtonFactory()
button = ['image', 'input', 'flash']
for b in button:
    print button_obj.create_button(b).get_html()

输出应该是所有按钮类型的HTML。

我得到这样的错误

AttributeError: 'str' object has no attribute 'baseclass'

我正在尝试实现一个具有不同变体的类,例如ImageButton,InputButton和FlashButton。根据地点的不同,可能需要为按钮创建不同的html

4 个答案:

答案 0 :(得分:13)

您正在尝试调用不存在的baseclass str属性,因为b获取字符串值(['image', 'input', 'flash']之一)。 如果要根据表示其名称的字符串创建对象,可以使用globals()字典,该字典包含变量名称及其值之间的映射。

class Button(object):
    html = ""
    def get_html(self):
        return self.html

class Image(Button):
    html = "<img></img>"

class Input(Button):
    html = "<input></input>"

class Flash(Button):
    html = "<obj></obj>"

class ButtonFactory():
    def create_button(self, typ):
        targetclass = typ.capitalize()
        return globals()[targetclass]()

button_obj = ButtonFactory()
button = ['image', 'input', 'flash']
for b in button:
    print button_obj.create_button(b).get_html()

编辑: 使用globals()locals()也不是一个好习惯,如果可以,最好在相关对象及其名称之间创建映射,如下所示:

button_objects = {'image':Image,'flash':Flash,'input':Input}

并将create_button替换为:

def create_button(self, typ):        
    return button_objects[typ]()

答案 1 :(得分:4)

以下是您的错误来源:

button = ['image', 'input', 'flash'] # button contains strings

for b in button: # b is a string

create_button(b) # argument 'type' is a string

type.baseclass... # hence the AttributeError

您的列表button需要包含具有baseclass属性的对象,而不是其名称作为字符串。此外,您不应将type用作变量名,因为它会影响Python标准库函数type()

答案 2 :(得分:3)

假设您需要基于运行时字符串创建按钮实例,Button类和工厂的替代方法是简单地为类型添加名称字典(就像chepner建议的那样):

buttonTypes = {"image" : Image,
               "input": Input,
               "flash" : Flash}

button = buttonTypes[name]()
print button.html

(这是直接输入到此处,细节中可能存在一些错误)。因为Python是鸭型的,所以你可能不需要基类型。

答案 3 :(得分:1)

截至 2021 年,已接受的答案可以通过多种方式改进,因为 Python 已经发展 (3.9.x)。这同样适用于其未记名的 original source

首先,对象工厂的实现与 interface 的概念紧密相关。根据 Gang of Four definition of the Factory Pattern 我们读到它应该:

<块引用>

定义用于创建对象的接口,但让子类决定 要实例化的类。工厂方法让类推迟 实例化到子类。

话虽如此,已接受的答案的一些基本改进可能如下:

  1. 基类 Button 应该是定义接口的抽象类。换句话说,它应该继承自 abc.ABC

  2. 我们看到子级 (Flash, Image, Input) 之间的唯一区别是每种情况下 self.html 属性的值,而不是get_html 方法的实际实现。这意味着 self.html 属性应该是抽象的,即接口的一部分。定义属性的一种非常简洁的方法是通过内置的 @property 装饰器。因此,抽象属性将在其“getter”方法上使用 @abc.abstractmethod@property 进行修饰。这很重要,因为如果“getter”方法没有在子类的主体内部实现,它会引发异常,有效地遵守接口规范。

  3. ButtonFactory.create_button 方法没有必要。其实也没有必要去实例化一个ButtonFactory,它的唯一目的就是为了产生子对象。相反,您可以在 ButtonFactory 的构造函数内(即在 __new__ 方法内)迁移子对象的创建。这将在工厂实例化时直接产生一个子对象。

  4. 最后,我们可以将所有按钮放在同一个命名空间(Buttons 类)下,以避免在查找任何子按钮时使用 globals

将所有内容放在一个 buttons.py 文件中:

from abc import ABC, abstractmethod


class Buttons:
    class Button(ABC):
        @property
        @abstractmethod
        def html(self) -> str:
            raise NotImplementedError

    class Flash(Button):
        html = "<obj></obj>"

    class Image(Button):
        html = "<img></img>"

    class Input(Button):
        html = "<input></input>"


class ButtonFactory:
    def __new__(cls, button_type: str) -> Buttons.Button:
        return getattr(Buttons, button_type)()

通过这种方式,我们可以将任何类似按钮的对象实例化为:

from buttons import ButtonFactory

flash_button = ButtonFactory("Flash")
image_button = ButtonFactory("Image")
input_button = ButtonFactory("Input")

print(flash_button.html, image_button.html, input_button.html)

输出将是:

<obj></obj> <img></img> <input></input>