简体   繁体   中英

Factory Design Pattern

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

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

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

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.

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:

  1. The base class Button should be an abstract class to define the interface. In other words, it should inherit from abc.ABC .

  2. 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, ie 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.

  3. 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 , ie inside the __new__ method. This would yield directly a child object at the instantiation of the factory.

  4. 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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM