【问题标题】:Factory Design Pattern工厂设计模式
【发布时间】:2014-01-09 16:35:19
【问题描述】:

我正在尝试实现工厂设计模式,并且到目前为止一直在这样做。

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

【问题讨论】:

  • 好吧,你将一个字符串(例如'image')作为参数传递给create_button()。也许您打算传递image(对具有baseclass 属性的实际类的引用)。
  • 您也可以使用不带参数的get_html,而它需要 1(self 除外)。目前尚不清楚您要从代码中做什么。
  • @Elisha 我正在尝试实现一个具有不同变体的类,例如 ImageButton、InputButton 和 FlashButton。根据位置的不同,可能需要为按钮创建不同的 html。
  • 您的问题已经得到解答,但我仍然想说,在普通函数可以使用的情况下使用类是非常不符合 Python 的。

标签: python design-patterns


【解决方案1】:

您正在尝试调用不存在的strbaseclass 属性,因为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]()

【讨论】:

  • 我强烈建议创建一个明确映射的字典,例如,'image'Image,而不是使用 eval 动态映射。或者更好的是,只需使用button = [ Image, Input, Flash ]:类是可以存储在列表中的一流对象。
  • 谢谢,我修复了代码以使用globals(),这样更安全并保留了使用字符串的能力。但我同意使用在字符串和对象之间映射的对象列表或字典要好得多。
  • 我不确定使用全局变量是否是一种改进。
  • 你能解释一下为什么吗?和locals()?无论如何,我在答案中添加了您的建议,谢谢。
  • 最后一行有错字。应该是return button_objects[typ]() 而不是buttun_objects。但这显然对于编辑来说太小了。
【解决方案2】:

这是您的错误的来源:

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()

【讨论】:

  • 好的,我明白了,但是你能提供一个例子吗?
  • 不是真的,我不知道你的对象是什么!
【解决方案3】:

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

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

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

(注意这里是直接输入的,细节上可能有一些错误)。因为 Python 是鸭子类型,所以你可能不需要基类型。

【讨论】:

  • 方括号在 Python 中不构造字典,大括号可以。
【解决方案4】:

截至 2021 年,由于 Python 已经发展,可接受的答案可能会以多种方式得到改进 (3.9.x)。这同样适用于其未经授权的original source

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

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

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

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

  2. 我们看到,孩子(FlashImageInput)之间的唯一区别是每种情况下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>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-20
    • 2010-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多