Factory Pattern in Python

Edited and approved by: Stefan Bradstreet

A Brief Introduction…

The problems you will try to solve as a programmer have a great likelihood of recurring throughout your software projects. This means that pieces of code will continuously need to be refined and updated as your program adds functionalities, adapts to shifting requirements, or eliminates bugs. Such refinement may continue for as long as the program is in service! While improving a program may spell good news for a CEO, it could be a nightmare for a developer! That is why the world of Software Development turns to design patterns.

Design patterns are general, reusable protocols and solutions for solving common problems. In programming terms, these show classes and objects and how they interact if a common problem is to be solved. Design patterns often show those solutions that are demonstrably the most efficient and have found prevalent usage in solving a specific problem in the past. Fortunately, one wouldn’t have to learn a new language to understand design patterns, they commonly carry the idea of a solution (pseudocode at the very most) and not its implementation. The idea is to improve code quality and to make your code more malleable, especially during problem-solving.

The Factory Pattern method falls under a class of design patterns, object creational patterns, where the programmer’s objective is to allow the client to create multiple similar instances of a class while the program achieves loose coupling which is one of the top 10 skills to be a better programmer. Breaking this down, the Python Factory Pattern method allows a class to create objects while subclasses decide which of those objects to instantiate. The key component here is a common interface, which subclasses can instantiate when needed. As the objects are created, the client is not exposed to the logic behind object creation but is, instead, directed to refer to the object through a common interface. In this factory pattern tutorial, we are going to see examples that help us to understand why design patterns are important and we will examine a few examples of simple factory pattern implementation.

Python Factory Pattern: Why Is It Important?

Consider two examples:

1. Imagine you are developing Google Translate. At first, you write the code that can translate English into Spanish, Italian, German and 6 of the most widely spoken languages in the world. If your application was built to work only with these 10 languages at first, adding a few more languages would be long, tedious work involving dismantling the current coupling of the program to only 10 languages and modifying the codebase to accommodate more languages. To make it worse, the world has thousands of more languages that would demand the same amount of work if they were to be included in your app. It would be tough to improve code quality without disturbing the existing code.

2. Consider the program below. It accepts a ‘movie’ object and returns it as a string of a specified format. For starters, our format could either be JSON or XML. One would imagine that we could create a method that accepts the object and required format as parameters and then use a simple conditional statement to return the required string. Conceptually speaking, this would work, and your code would look something like this:

import json
import xml.etree.ElementTree as et

class Movie:
  def __init__(self, movie_id, title, director):
    self.movie_id = movie_id
    self.title = title
    self.director = director

class MovieToText:
  def convert(self, movie, format):
    if format == 'XML':
      movie_info = et.Element(movie, attrib={'id': movie.movie_id})
      title = et.SubElement(movie_info, 'title')
      title.text = movie.title
      director = et.SubElement(movie_info, 'director’)
      director.text = movie.director
      return json.dumps(movie_info)
    elif format == 'JSON':
      movie_info = {
        'id': movie.movie_id,
        'title': movie.title,
        'director’: movie.director
         }
       return et.tostring(movie_info, encoding='unicode')
    else:
      raise ValueError(format)

Above, our program can, at first, convert objects into one of two formats: JSON or XML. Anything outside this scope gives an exception. Running this in the shell would give output that looks similar to this:

>>> import serialiser_demo as sd
>>> movie = sd.Movie( '1' , 'Django' , 'Quentin Tarantino' )
>>> convert = sd.MovieToText( )
>>> serializer.convert( movie, 'JSON' )
'{"id": "1", "title": "Django", "producer": "Quentin Tarantino"}'
>>> serializer.convert( movie, 'XML' )
'<movie id="1"><title>Django</title><producer>Quentin Tarantino</producer></movie>'
>>> serializer.convert( movie, 'HTML' )
>>> serializer.serialize(song, 'YAML')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "./serializer_demo.py", line 30, in serialize
raise ValueError(format)
ValueError: HTML

The code above, while, to some extent, functional , has some fundamental flaws. First of all, it violates the single responsibility principle, that limits one module, function or class to one specific purpose. In serving multiple purposes through the webbed conditional statements, this code is all but too easy to render obsolete. Think, for a second, how useful this code would be if a new format was introduced. The function would have to change. Additionally, if our objects changed, perhaps carrying a few additional instance variables such as ‘release_date’ or ‘movie_length’, the movie class would need alteration. And what if the name of the format changes with future updates? Again, the code would need amendments. Wouldn’t it be much easier if all those changes could occur without us having to edit the original code? Enter Python Factory Pattern!

Simple Python Factory Pattern Implementation

When our program’s behavior follows different logical paths, our goal is to look for a common interface to sit in place of those paths and to concretely decide the necessary implementation depending on the parameters requested by the user. Above, we would look for an interface to provide implementations for JSON and XML formats. The decision on which implementation to follow comes from a separate component we create. This creator component should be able to read the specified format.

Implementing a simple Python Factory Pattern follows 3 main steps:

  1. Refactoring our original code to make it simpler to read
  2. Noting our concrete implementations
  3. Adding the creator component that decides which implementation to follow

1. Refactoring

Below is the refactored code for JSON conversion (the same can be done for XML conversion):

class MovieToText:
  def convert(self, movie, format):
    if format == 'JSON':
      return self._convert_to_json(movie)
    # The rest of the code is the same
    # Add new function, _convert_to_json, and add conversion code to it 
    # instead of keeping it here. Call
    # conversion from here
  def _convert_to_json(self, movie):
    payload = {
      'id': movie.movie_id,
      'title': movie.title,
      'director': movie.director
    }
    return json.dumps(payload)

After refactoring, we note our concrete implementations.

2. Identifying Implementations

Refactoring the code cited earlier should give you 3 methods: ‘convert’, ‘_convert_to_json’ and ‘_convert_to_xml’. The concrete implementations are ‘_convert_to_json’ and ‘_convert_to_xml.’ These are the methods for which you need a creator component, which will select an appropriate one for a given format.

3. Adding The Creator Component

Finally, we add a function that checks the format in order to choose the appropriate logical pathway. In this case, let it be the ._get_converter( ) method. It may look something like this:

class MovieToText:
  def _get_converter(self, format):
    if format == 'JSON':
      return self._convert_to_json
    elif format == 'XML':
      return self._convert_to_xml
    else:
      raise ValueError(format)

Your final implementation would look like this:

import json
import xml.etree.ElementTree as et


class MovieToText:

  def convert(self, movie, format):
    converter = self._get_converter( format )

  def _get_converter(self, format):
    if format == 'JSON':
      return self._convert_to_json
    elif format == 'XML':
      return self._convert_to_xml
    else:
      raise ValueError(format)
  
  def _convert_to_json(self, movie):
    payload = {
      'id': movie.movie_id,
      'title': movie.title,
      'director: movie.director
    }
    return json.dumps(payload)

  def _convert_to_xml(self, movie):
    movie_info = et.Element(movie, attrib={'id': movie.movie_id})
    
    title = et.SubElement(movie_info, 'title')
    title.text = movie.title
    
    director = et.SubElement(movie_info, 'director’)
    director.text = movie.director
    
    return et.tostring( movie_info, encoding='unicode' )

Above, we see:

  • The method that is dependent on an interface ( .convert( ) ), called the client.
  • The interface, or product, which is the function which accepts a movie and returns a string
  • The concrete functions
  • creator component ( ._get_converter( ) ) which returns the appropriate function to use
  • An identifier for the appropriate implementation to be used (format)

Interestingly, this code, when run, will give similar results to the implementation we tried earlier on.

Applications

The Python Factory Pattern is recommended to use in situations where an application runs on an interface, and the implementation to be followed depends on a specified identifier. Cases where we need to instantiate objects depending on a user’s input also qualify. Cases where an application has similar features also allow the programmer to bind them under a common interface which can instantiate these dependent on some indicator.

Remember, implementing the factory design pattern works to improve code quality and carries the underlying value of allowing developers to cope with the evolving needs of a program/application with minimal disturbances to the underlying code. Design patterns are programming ‘precedents’ that can used when similar problems occur in the future. Regarding what has been covered in this factory pattern tutorial, our code can be used if parameters change, when identifier names are modified or when we need methods for new functionalities. No tutorial would be complete, though, without the recommendation to practice typing out and analyzing the above implementation by yourself. It will help the concept stick. Have fun with Python Factory Pattern designs!

About Stefan Bradstreet

Stefan is a software development engineer II at Amazon with 5+ years of experience in tech. He is passionate about helping people become better coders and climbing the ranks in their careers as well as his own through continued learning of leadership techniques and software best practices.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s