Abstract Factory Design Pattern in Python
What is it?
Abstract Factory Design Pattern essentially builds on the Factory Design Pattern. It adds another level of encapsulation. It aims at providing the user with a way of creating objects of related classes (i.e. having a common characteristic, such as Knight, Rook etc.) at a given instance without exposing the exact class that is being instantiated, up until runtime. It is classified under Creational Patterns as it provides an industry standard procedure to create objects. It makes use of Polymorphism.
Let's look at the terms associated with this pattern.
Terminology
- Concrete Factory: A class which has a getter method, which returns an object of a class. The latter class, is one of many classes that denote different types of objects having a common characteristic (such as Knight, Rook, Bishop, they are chess pieces). For example, KnightFactory is a concrete factory which returns an object of type Knight.
- Abstract Factory: A class which takes an object of one of the concrete classes as input, gets the object from it using the getter method of the concrete class, and provides a method to expose the details of the thus obtained object.
Pseudo Code
# Classes denoting different types of objects having a common characteristic. class Knight: def directionOfMovement(): pass class Rook: def directionOfMovement(): pass # Concrete Classes: returns an object of the corresponding class class KnightFactory: def getPiece(): returns a Knight object class RookFactory: def getPiece(): returns a Rook object # Abstract Factory: takes a concrete factory object as input, obtains the object from the factory, and provides a method to expose details of the object. class PieceFactory: def __init__(): assign the supplied concrete factory object to a local variable def detailsOfChosenPiece(): call the getPiece() of the concrete factory object to obtain a piece access different methods and attributes of the piece such as directionOfMovement() # USING THE CODE create object of a concrete factory, say, RookFactory create object of the abstract factory and supply to it the above object using the above object of the abstract factory, call the utility method of the abstract factory i.e. detailsOfChosenPiece() objectOfConcreteFactory = RookFactory() objectOfAbstractFactory = PieceFactory(objectOfConcreteFactory) objectOfAbstractFactory.detailsOfChosenPiece()
Why the need for it: Problem Statement
Last time around, we looked at the Simple Factory type of Factory Design Pattern. The Abstract Factory Pattern is another type of Factory Design Pattern, aiming at providing another level of encapsulation to the code. It addresses the same problems as the Simple Factory Pattern i.e. violation of Open/Closed Principle and the client having to be aware of all the concrete classes. The Abstract Factory Pattern is implemented when the user needs to be provided with a way of creating objects of related classes (i.e. having a common characteristic, such as Knight, Rook etc.) at a given instance without exposing the exact class that is being instantiated, up until runtime.
How to implement it
The Abstract Factory Pattern can be implemented in Python using:
- Different classes denoting different types of objects having a common characteristic.
- Concrete Factories having getter methods to return objects of above classes.
- Abstract Factory which takes a concrete factory object as input, obtains the object from the factory, and provides a method to expose details of the object.
# Different classes denoting different types of objects having a common characteristic: Knight, Rook & Bishop class Knight: '''One of many classes having a common characteristic''' def directionOfMovement(self): return "Neither horizontally nor vertically." def stepsInMovement(self): return "2 and a half." def __str__(self): return "Knight" class Rook: '''One of many classes having a common characteristic''' def directionOfMovement(self): return "Horizontally or vertically." def stepsInMovement(self): return "As many as 7." def __str__(self): return "Rook" class Bishop: '''One of many classes having a common characteristic''' def directionOfMovement(self): return "Diagonally." def stepsInMovement(self): return "As many as 7." def __str__(self): return "Bishop" # Concrete Factories having getter methods to return objects of above classes: KnightFactory, RookFactory & BishopFactory class KnightFactory: '''Concrete Factory based on a class; returns an object of the corresponding class''' def getPiece(self): return Knight() class RookFactory: '''Concrete Factory based on a class; returns an object of the corresponding class''' def getPiece(self): return Rook() class BishopFactory: '''Concrete Factory based on a class; returns an object of the corresponding class''' def getPiece(self): return Bishop() # Abstract Factory which takes a concrete factory object as input, obtains the object from the factory, and provides a method to expose details of the object: Piece Factory class PieceFactory: '''An abstract factory which takes a concrete factory object as input, obtains the object from the factory, and provides a method to expose details of the object. ''' def __init__(self, pieceFactory): self._pieceFactory = pieceFactory def detailsOfChosenPiece(self): '''utility method to display details of object returned by the abstract factory''' chosenPiece = self._pieceFactory.getPiece() print("Chosen piece:", chosenPiece) print("Direction of chosen piece:", chosenPiece.directionOfMovement()) print("Number of steps the chosen piece can move:", chosenPiece.stepsInMovement()) objectOfConcreteFactory = RookFactory() objectOfAbstractFactory = PieceFactory(objectOfConcreteFactory) objectOfAbstractFactory.detailsOfChosenPiece() ### OUTPUT ### Chosen piece: Rook Direction of chosen piece: Horizontally or vertically. Number of steps the chosen piece can move: As many as 7.
Walkthrough of implementation
- We obtain an object of one of the Concrete Factory (RookFactory) classes (objectOfConcreteFactory = RookFactory())
- Then, we supply the above object to the Abstract Factory (PieceFactory) and obtain its object(objectOfAbstractFactory = PieceFactory(objectOfConcreteFactory)). The __init__() method is called implicitly during object creation, which stores the object of the supplied concrete factory in a local variable.
- Using this object of the Abstract Factory, we obtain an object of the class Rook by making use of the utility method of the Abstract factory detailsOfChosenPiece(). This method first obtains the object of the class Rook using the getter method of the Concrete Factory RookFactory (chosenPiece = self._pieceFactory.getPiece()). Then, various methods of the object of class Rook are accessible.
Comparison of code when it is implemented and when it is not
BEFORE ABSTRACT 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') pieceOne.someMethodOfKnightClass()
AFTER ABSTRACT FACTORY PATTERN:
# Classes denoting different types of objects having a common characteristic. class Knight: def directionOfMovement(): pass class Rook: def directionOfMovement(): pass # Concrete Classes: returns an object of the corresponding class class KnightFactory: def getPiece(): returns a Knight object class RookFactory: def getPiece(): returns a Rook object # Abstract Factory: takes a concrete factory object as input, obtains the object from the factory, and provides a method to expose details of the object. class PieceFactory: def __init__(): assign the supplied concrete factory object to a local variable def detailsOfChosenPiece(): call the getPiece() of the concrete factory object to obtain a piece access different methods and attributes of the piece such as directionOfMovement() # USING THE CODE create object of a concrete factory, say, RookFactory create object of the abstract factory and supply to it the above object using the above object of the abstract factory, call the utility method of the abstract factory i.e. detailsOfChosenPiece() objectOfConcreteFactory = RookFactory() objectOfAbstractFactory = PieceFactory(objectOfConcreteFactory) objectOfAbstractFactory.detailsOfChosenPiece()
Related to: Factory
- Creational Patterns
- Factory
- Abstract Factory
- Prototype
- Singleton
- Builder
- Architectural Pattern
- Model View Controller (MVC)