The Factory pattern
During the development of large applications, there are certain cases where we might want to initialize a class dynamically, based upon the user input or some other dynamic factor. To achieve this, either we can initialize all the possible objects during the class instantiation and return the one that is required based on the inputs from the environment, or we can altogether defer the creation of class objects until an input has been received.
The Factory pattern is the solution to the latter case, where we develop a special method inside a class, which will be responsible for initializing the objects dynamically, based on the input from the environment.
Now, let's see how we can implement the Factory pattern in Python in a simplistic way, and then we will see how we can use the dynamic nature of Python to make our Factory class more dynamic.
So, let's take an example of an HTML Form Component, which needs to be rendered on a web page. Let's see how we can deal with this using Python classes and the Factory pattern:
from abc import ABC, abstractmethod
class HTMLFormEntity(ABC):
def __init__(self, id, name):
self.id = id
self.name = name
@abstractmethod
def render(self):
pass
class Button(HTMLFormEntity):
def __init__(self, id, name):
super().__init__(id, name)
self.type = "button"
def render(self):
html = "<input id={idx} name={name} type={itype} />"
return html.format(idx=self.id, name=self.name, itype=self.type)
class Text(HTMLFormEntity):
def __init__(self, id, name):
super().__init__(id, name)
self.type = "text"
def render(self):
html = "<input id={idx} name={name} type={itype} />"
return html.format(idx=self.id, name=self.name, itype=self.type)
class HTMLForm(HTMLFormEntity):
__elements = {
"button": Button,
"text": Text
}
def __init__(self, id, name, action, method):
super().__init__(id, name)
self.action = action
self.method = method
self.elements = []
def add_entity(self, entity_type, id, name):
if entity_type in self.__elements.keys():
self.elements.append(self.__elements[entity_type](id, name))
def render(self):
# render method body
In this example, we can see how we implement the Factory pattern in our HtmlForm class. The class contains a mapping of classes that we may want to dynamically instantiate. Inside the HtmlForm class, the add_entity method acts as our factory method, which checks if the object being asked for inclusion in our form is present inside the dictionary of possible elements we can instantiate or not, and then, if it is present, it is instantiated and added to the list of elements that need to be rendered, when the form is rendered.
Now, this approach has one drawback. Whenever we want to support a new HTML Form element, we will have to modify our HtmlForm class to add a mapping for the element. This process is cumbersome, and there should be a better way to deal with this situation. So let's take a look at how we can exploit the dynamic nature of Python to make our add_entity method more dynamic.
Let's re-implement our HtmlForm class to be a bit more dynamic here:
from importlib import import_module
class HTMLForm(HTMLFormEntity):
def __init__(self, id, name, action, method):
super().__init__(id, name)
self.action = action
self.method = method
self.elements = []
def add_entity(self, entity_type, id, name):
module_name = entity_type
class_name = module_name.capitalize()
module = import_module('.'+module_name, package="bugzot")
class = getattr(module, class_name)
self.elements.append(class(id, name))
def render(self):
# render method implementation
In this example, we modified the implementation of our add_entity method that now does not need to know which classes can be dynamically instantiated. To do this, we used the importlib library from Python. The import_module method from the importlib can be used to dynamically import the modules from our application, and then we can retrieve the classes from the imported module. Once the class is imported, all we need to do is to instantiate an object from the class, and then add it to our list of elements that need to be rendered.
This kind of approach not only makes the implementation of the Factory method simple, but also helps the developer to allow for the addition of new modules easily, without modifying the factory class.
We have now seen how the Factory pattern works, but what are some of the possible use cases where the Factory pattern can be of help? These include the following:
- When we don't know which of the classes need to be instantiated in advance.
- When future changes are required in the application, the Factory pattern can help in keeping the object creation logic segregated from how the class implementation has been done.
Now, let's take a look at one of the most common design patterns, the Model-View-Controller (MVC) pattern, which is used in many applications and a lot of web frameworks.