工厂设计模式

16

我正在尝试实现工厂设计模式,目前为止已经做到了这一步。

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 属性)。 - chepner
你在代码中使用了没有参数的 get_html,但它需要一个参数(除了 self)。从你的代码中不清楚你想要做什么。 - Elisha
@Elisha 我正在尝试实现一个类,它有不同的变体,比如ImageButton、InputButton和FlashButton。根据位置的不同,可能需要为按钮创建不同的HTML。 - ajknzhol
你的问题已经得到了回答,但我还想说,当一个简单的函数就能胜任时,使用类是相当不符合Python风格的。 - bruno desthuilliers
4个回答

14
你正在尝试调用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]()

5
我强烈建议创建一个字典,明确地将例如'image'映射到Image,而不是使用eval动态执行。或者更好的方法是,只需使用button = [ Image, Input, Flash ]:类是一等对象,可以存储在列表中。 - chepner
谢谢,我已经修复了代码,使用globals()更安全,并保持了使用字符串的能力。但我同意使用对象列表或将字符串和对象映射的字典会更好。 - Elisha
我不确定使用全局变量是否会有所改进。 - chepner
你能解释一下为什么要用locals()吗?无论如何,我已经在答案中加入了你的建议,谢谢。 - Elisha
最后一行有一个打字错误。应该是 return button_objects[typ]() 而不是 buttun_objects。但这个变化太小了,无法编辑。 - nik
显示剩余2条评论

5
截至2021年,由于Python发展(3.9.x),可以通过多种方式改进已接受的答案。同样适用于未获认可的原始来源
首先,对象工厂的实现与接口的概念紧密相连。根据四人帮设计模式的定义,我们可以看到它应该:

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

话虽如此,已接受的答案需要一些基本的改进,包括以下内容:
  1. 基类 Button 应该是一个抽象类来定义接口。换句话说,它应该继承自 abc.ABC

  2. 我们可以看到子类之间 (Flash, Image, Input) 唯一的区别是每种情况下 self.html 属性的值而不是 get_html 方法的实际实现。这意味着 self.html 属性应该是抽象的,即接口的一部分。通过内置的 @property 装饰器非常巧妙地定义属性。因此,抽象属性将使用 @abc.abstractmethod@property 在其 "getter" 方法上进行装饰。这很重要,因为如果 "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>

3
这里是您错误的来源:
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()


好的,我明白了,但是你能提供一个例子吗? - ajknzhol
不是很清楚,我不知道你的对象是什么! - jonrsharpe

3
假设您需要根据运行时字符串创建按钮实例,与您的Button类和工厂相比,一种替代方法是仅具有名称到类型的字典(就像chepner建议的那样):
buttonTypes = {"image" : Image,
               "input": Input,
               "flash" : Flash}

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

因为Python是鸭子类型的,所以您可能不需要基本类型。请注意,此内容是直接键入的,细节可能存在一些错误。

在Python中,方括号不构建字典,而花括号则是。 - Terrence Brannon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接