Factory

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon
Reading Time: 4 minutes

Factory Design Pattern in Python

Factory Design Pattern in Python

Design Patterns Home
 

What is it?

Factory Design Pattern provides one of the most hassle-free ways to create objects. It makes use of Polymorphism. The hassle-free creation of objects is achieved by creating the object using a method (known as Factory Method) without exposing the creation logic to the user. That is, the user does not have to know how the object is being created. The user does not need to specify the exact class of the object that needs to be created.

It is classified under Creational Design Patterns, since it provides one of the best ways to instantiate classes.


Why the need for it: Problem Statement

By now, we know that the Factory Pattern is related to creation of objects. Factory Pattern is most needed in the following four situations:

  1. When you wish to separate the creation logic of objects from their place of use
  2. When you have many if/else clauses to decide which object to create
  3. When you have many switch statements to decide which object to create (not applicable in Python)
  4. When you are not sure which concrete implementation of an interface to execute (not applicable in Python)

For example:

class ChessPieceFactory:
def createChessPiece(self, inputString):
if inputString == "knight":
return Knight()
elif inputString == "rook":
return Rook()
elif inputString == "bishop":
return Bishop()

chessPieceFactory = ChessPieceFactory
pieceOne = chessPieceFactory.createChessPiece('knight')

But why do we need to implement a pattern when the above code is working perfectly fine and is not bad at readability at all? You are right, the code works and is readable as well, but in the event that you need to manufacture a new piece, say a King, you will have to insert another clause to the code. This leads to violation of The Open/Closed Principle, which states that

software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification, that is, such an entity can allow its behaviour to be extended without modifying its source code.

~ Wikipedia

To add a new piece, we have to change the if construct, which is a modification to the code. This is the problem we are addressing here. It's not a matter of working and erronous code, it is about the software principle involved, i.e. Open/Closed principle. Additionally, in the above setup, the user needs to be aware of all the concrete classes i.e. Knight, Rook, Bishop. So, there's a dependency that each time a new concrete class is created, the user needs to made aware of it too.


Solution in a nutshell

Now that we have seen the problems involved, how to go about solving it? The Factory Pattern, if implemented, handles both these problems. It keeps conformity of your code with the Open/Closed Principle, in addition to removing user's dependency on concrete classes. This is achieved by providing the user an interface, which is responsible for creating the chess piece. Let's look at the following code:

def makeChessPiece(inputFromCreateChessPiece):
if inputFromCreateChessPiece == "knight":
return Knight()
elif inputFromCreateChessPiece == "rook":
return Rook()
elif inputFromCreateChessPiece == "bishop":
return Bishop()

class ChessPieceFactory:
def createChessPiece(self, inputString):
chessPiece = makeChessPiece(inputString)
return chessPiece

chessPieceFactory = ChessPieceFactory()
pieceOne = chessPieceFactory.createChessPiece('knight')

This is the Simple Factory in action. An interface 'makeChessPiece' has been provided to the user to create the pieces, with all the creation logic encapsulated from him. This interface is called a Factory Method. If a new concrete class has to be added, it will be added in this interface and not in the class and thereby, ridding the user of the dependency on creation of new classes. This keeps the code of the ChessPieceFactory as it is in the event of adding a new chess piece class.


Terminology

  • Factory method: A method which returns an object of one of several classes based on an input. These classes denote different types of objects having a common characteristic, such as Knight, Rook, Bishop. All these have a common characteristic that they are chess pieces.

How to implement it

The above logic can be realized in Python using a dictionary. You will need:

  1. Different classes denoting different types of objects having a common characteristic.
  2. A Factory Method that returns the object based on an input.
# Different classes denoting different types of objects having a common characteristic: Knight, Rook & Bishop
class Rook:
'''A simple Rook class'''
def move(self):
return "I move any number of spaces but only horizontally or vertically."

class Knight:
'''A simple Knight class'''
def move(self):
return "I move 2 and half spaces."

class Bishop:
'''A simple Bishop class'''
def move(self):
return "I move any number of spaces diagonally."

# A Factory Method that returns the object based on an input.
def makeChessPiece(piece):
'''Factory method that takes a string and returns an object based on it.'''
pieces = {"knight": Knight(), "bishop": Bishop(), "rook": Rook()}
return pieces[piece]

class ChessPieceFactory:
def createChessPiece(self, inputString):
createdPiece = makeChessPiece(inputString)
return createdPiece

chessPieceFactory = ChessPieceFactory()

pieceOne = chessPieceFactory.createChessPiece('knight')
print("Knight:", pieceOne.move())

pieceTwo = chessPieceFactory.createChessPiece('bishop')
print("Bishop:", pieceTwo.move())

pieceThree = chessPieceFactory.createChessPiece('rook')
print("Rook:", pieceThree.move())

### OUTPUT ###
Knight: I move 2 and half spaces.
Bishop: I move any number of spaces diagonally.
Rook: I move any number of spaces but only horizontally or vertically.

Walkthrough of implementation

  1. We first pass the string 'knight' to the makeChessPiece() method. The dictionary gives an object of class Knight based on the key 'knight'. This object is returned from the function and assigned to the variable pieceOne. Then we access the move() method of pieceOne which is actually an object of class Knight.
  2. We then pass the string 'bishop' to the makeChessPiece() method. The dictionary gives an object of class Bishop based on the key 'bishop'. This object is returned from the function and assigned to the variable pieceTwo. Then we access the move() method of pieceTwo which is actually an object of class Bishop.
  3. We then pass the string 'rook' to the makeChessPiece() method. The dictionary gives an object of class Rook based on the key 'rook'. This object is returned from the function and assigned to the variable pieceThree. Then we access the move() method of pieceThree which is actually an object of class Rook.

Comparison of code when it is implemented and when it is not

BEFORE FACTORY PATTERN:

class ChessPieceFactory:
def createChessPiece(self, inputString):
if inputString == "knight":
return Knight()
elif inputString == "rook":
return Rook()
elif inputString == "bishop":
return Bishop()

chessPieceFactory = ChessPieceFactory
pieceOne = chessPieceFactory.createChessPiece('knight')

AFTER FACTORY PATTERN:

def makeChessPiece(inputFromCreateChessPiece):
if inputFromCreateChessPiece == "knight":
return Knight()
elif inputFromCreateChessPiece == "rook":
return Rook()
elif inputFromCreateChessPiece == "bishop":
return Bishop()

class ChessPieceFactory:
def createChessPiece(self, inputString):
chessPiece = makeChessPiece(inputString)
return chessPiece

chessPieceFactory = ChessPieceFactory()
pieceOne = chessPieceFactory.createChessPiece('knight')

The last two lines, where we are essentially manufacturing the chess pieces are the same, but the results are achieved differently. By implementing the Factory Pattern, we are not violating the Open/Closed Principle, and we are ridding the user of any responsibility of actually knowing all the classes.


 

 

 

 


See also:

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon

Leave a Reply