Reputation: 6460
I am trying to implement Factory Design Pattern and have done this far till now.
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()
The output should be the HTML of all your button types.
I get the error like this
AttributeError: 'str' object has no attribute 'baseclass'
I am trying to implement a class which has different variations, such as ImageButton, InputButton and FlashButton. Depending on the place, it may need to create different html for the buttons
Upvotes: 17
Views: 26289
Reputation: 991
As of 2021, the accepted answer could be improved in multiple ways because Python has evolved (3.9.x
). The same applies to its uncredited original source.
First of all, the implementation of an object factory is tightly bound to the notion of an interface. According to the Gang of Four definition of the Factory Pattern we read that it should:
Define an interface for creating an object, but let subclasses decide which class to instantiate. The factory method lets a class defer instantiation to subclasses.
Having said that, some essential improvements of the accepted answer could be the following:
The base class Button
should be an abstract class to define the interface. In other words, it should inherit from abc.ABC
.
We see that the only difference between the children (Flash
, Image
, Input
) is the value of the self.html
attribute in each case and not the actual implementation of the get_html
method. This means that the self.html
attribute should be abstract, i.e. a part of the interface. A very neat way of defining attributes is via the built-in @property
decorator. Thus, an abstract attribute would be decorated using both @abc.abstractmethod
and @property
on its "getter" method. This is important, because it will raise an exception if the "getter" method is not implemented inside the body of the child class, effectively adhering to the interface specifications.
The ButtonFactory.create_button
method is unnecessary. In fact, it is also unnecessary to instantiate a ButtonFactory, whose exclusive purpose is to yield child objects. Instead, you could migrate the creation of a child object within the constructor of a ButtonFactory
, i.e. inside the __new__
method. This would yield directly a child object at the instantiation of the factory.
Finally, we can put all buttons under the same namespace (Buttons
class) to avoid using globals
when looking for any of the children buttons.
Putting everything together in a buttons.py
file:
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)()
In this way, we could instantiate any button-like object as:
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)
and the output would be:
<obj></obj> <img></img> <input></input>
Upvotes: 5
Reputation: 1323
Assuming you need to create button instances based on a runtime string, an alternative to your Button class and factory would be to simply have a dictionary of names to types (much as chepner suggested):
buttonTypes = {"image" : Image,
"input": Input,
"flash" : Flash}
button = buttonTypes[name]()
print button.html
(nb this was typed straight into here, there may be some mistakes in the detail). Because Python is duck-typed you may well not need a base type.
Upvotes: 3
Reputation: 4961
You are trying to call baseclass
attribute of str
, which does not exist, because b
gets string values (one of ['image', 'input', 'flash']
).
If you want to create an object according to a string representing its name, you can use the globals()
dictionary, which holds a mapping between variable names and their values.
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()
EDIT:
Using globals()
or locals()
is also not a good practice so, if you can, it is better to create a mapping between the relevant objects and their names, like this:
button_objects = {'image':Image,'flash':Flash,'input':Input}
and replace create_button
with:
def create_button(self, typ):
return button_objects[typ]()
Upvotes: 14
Reputation: 122169
Here is where your error comes from:
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
Your list button
needs to contain objects that have the baseclass
attribute, not their names as strings. Also, you shouldn't use type
as a variable name, as it shadows a Python standard library function type()
.
Upvotes: 3