我正在尝试实施工厂设计模式,并且到现在为止已经做到了这一点。
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
答案 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 我们读到它应该:
<块引用>定义用于创建对象的接口,但让子类决定 要实例化的类。工厂方法让类推迟 实例化到子类。
话虽如此,已接受的答案的一些基本改进可能如下:
基类 Button
应该是定义接口的抽象类。换句话说,它应该继承自 abc.ABC
。
我们看到子级 (Flash
, Image
, Input
) 之间的唯一区别是每种情况下 self.html
属性的值,而不是get_html
方法的实际实现。这意味着 self.html
属性应该是抽象的,即接口的一部分。定义属性的一种非常简洁的方法是通过内置的 @property
装饰器。因此,抽象属性将在其“getter”方法上使用 @abc.abstractmethod
和 @property
进行修饰。这很重要,因为如果“getter”方法没有在子类的主体内部实现,它会引发异常,有效地遵守接口规范。
ButtonFactory.create_button
方法没有必要。其实也没有必要去实例化一个ButtonFactory,它的唯一目的就是为了产生子对象。相反,您可以在 ButtonFactory
的构造函数内(即在 __new__
方法内)迁移子对象的创建。这将在工厂实例化时直接产生一个子对象。
最后,我们可以将所有按钮放在同一个命名空间(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>