Scripting

Abstract Factory – Design Patterns in Python

Abstract Factory – Design Patterns in Python

Intent

Abstract Factory is a creational design pattern that lets us produce families of related objects without specifying their concrete classes. It means that Abstract Factory allows a class to return a factory of classes, and for this reason, Abstract Factory is considered as one level higher than The Factory Method design pattern.

Problem

Suppose you are developing a Furniture e-commerce website like Pepperfry which sells chairs, tables, and sofas both as individual and combo. There are different variants of chairs, tables, and sofas, for e.g. Victorian and Modern. Now for the victorian variant, your code needs to implement different classes like VictorianChair, VictorianTable, VictorianSofa, and similarly for the modern variant.

This seems good as of now, but if you want to add more furniture family variants like Contemporary, Traditional, etc. then you have to implement their corresponding furniture classes and have to modify most of your existing code to support these variants. The growing number of furniture classes also makes your code very lengthy and messy. Also, customers get quite mad when they receive non-matching furniture.

Solution

According to the Abstract Factory design pattern –

  1. You need to declare interface or abstract classes for individual products like Chair, Sofa, and Table. By implementing these interfaces (or extending these abstract classes) you can make all variants of products. For example, extending the Chair abstract class you can make Victorian Chair, ModernChair, etc.
  2. Now, for each variant of a product family, you need to create a separate factory class based on the AbstractFactory interface or abstract class. A factory is a class that returns products of a particular kind. For example, the VictorianFurnitureFactory can only create VictorianChair, VictorianSofa, and VictorianTable objects.

The client code is exposed to products and factories via their corresponding abstract classes or interfaces. This enables you to change the type of factory that passed to the client code, as well as the product variant that the client code receives, without breaking the actual client code.

Code

import abc

from enum import Enum

class FurnitureComboType(Enum):

VICTORIAN = 0

MODERN = 1

class Chair:

@abc.abstractmethod

def hasLegs(self) -> bool:

pass

@abc.abstractmethod

def hasSeatCushion(self) -> bool:

pass

@abc.abstractmethod

def hasBackCushion(self) -> bool:

pass

@abc.abstractmethod

def getMaterialType(self) -> str:

pass

class Sofa:

@abc.abstractmethod

def getNumberOfSeats(self) -> int:

pass

@abc.abstractmethod

def canBeConvertedToBed(self) -> bool:

pass

@abc.abstractmethod

def canBeFolded(self) -> bool:

pass

@abc.abstractmethod

def getMaterialType(self) -> str:

pass

class Table:

@abc.abstractmethod

def getWidth(self) -> float:

pass

@abc.abstractmethod

def getHeight(self) -> float:

pass

@abc.abstractmethod

def getTopMaterialType(self) -> str:

pass

@abc.abstractmethod

def getBaseMaterialType(self) -> str:

pass

class VictorianChair(Chair):

def hasLegs(self) -> bool:

return True

def hasSeatCushion(self) -> bool:

return True

def hasBackCushion(self) -> bool:

return True

def getMaterialType(self) -> str:

return "Polished Mahogany Wood"

class VictorianSofa(Sofa):

def getNumberOfSeats(self) -> int:

return 3

def canBeConvertedToBed(self) -> bool:

return False

def canBeFolded(self) -> bool:

return False

def getMaterialType(self) -> str:

return "Polished Mahogany Wood"

class VictorianTable(Table):

def getWidth(self) -> float:

return 2

def getHeight(self) -> float:

return 3

def getTopMaterialType(self) -> str:

return "Polished Mahogany Wood"

def getBaseMaterialType(self) -> str:

return "Polished Mahogany Wood"

class ModernChair(Chair):

def hasLegs(self) -> bool:

return False

def hasSeatCushion(self) -> bool:

return True

def hasBackCushion(self) -> bool:

return True

def getMaterialType(self) -> str:

return "Vinyl"

class ModernSofa(Sofa):

def getNumberOfSeats(self) -> int:

return 2

def canBeConvertedToBed(self) -> bool:

return True

def canBeFolded(self) -> bool:

return True

def getMaterialType(self) -> str:

return "Leather"

class ModernTable(Table):

def getWidth(self) -> float:

return 3

def getHeight(self) -> float:

return 4

def getTopMaterialType(self) -> str:

return "Glass"

def getBaseMaterialType(self) -> str:

return "Solid Wood"

class FurnitureFactory:

@abc.abstractmethod

def getChair(self) -> Chair:

pass

@abc.abstractmethod

def getSofa(self) -> Sofa:

pass

@abc.abstractmethod

def getTable(self) -> Table:

pass

class VictorianFurnitureFactory(FurnitureFactory):

def __init__(self) -> None:

self._chair = VictorianChair()

self._sofa = VictorianSofa()

self._table = VictorianTable()

def getChair(self) -> Chair:

return self._chair

def getSofa(self) -> Sofa:

return self._sofa

def getTable(self) -> Table:

return self._table

class ModernFurnitureFactory(FurnitureFactory):

def __init__(self) -> None:

self._chair = ModernChair()

self._sofa = ModernSofa()

self._table = ModernTable()

def getChair(self) -> Chair:

return self._chair

def getSofa(self) -> Sofa:

return self._sofa

def getTable(self) -> Table:

return self._table

class FurnitureFactoryCreator:

def getFurnitureCombo(self, requestedComboType: FurnitureComboType) -> FurnitureFactory:

if requestedComboType == FurnitureComboType.VICTORIAN:

return VictorianFurnitureFactory()

elif requestedComboType == FurnitureComboType.MODERN:

return ModernFurnitureFactory()

else:

return None

if __name__ == "__main__":

furnitureCombo = FurnitureFactoryCreator().getFurnitureCombo(FurnitureComboType.VICTORIAN)

chair = furnitureCombo.getChair()

sofa = furnitureCombo.getSofa()

table = furnitureCombo.getTable()




print(f"Chair Material Type: {chair.getMaterialType()}")

print(f"Number of Sofa seats: {sofa.getNumberOfSeats()}")

print(f"Table top Material Type: {table.getTopMaterialType()}")

Output

Applicability

Use the Abstract Factory design pattern when your code needs to work with various families of related products, but the concrete classes of those products might be unknown beforehand or you simply want your code to be extensible in the future.

Pros and Cons

Pros Cons
It decouples concrete products. From the client code. It introduces a lot of new abstract classes or interfaces to implement this pattern which increases complexity.
It ensures that the products getting from a factory are compatible with each other.
It follows the Single Responsibility Principle. It moves the product creation code into one place in the program, which makes it easier to support the code.
It follows the Open/Closed Principle. You can easily extend the application for new variants of products without breaking the existing code.

The Abstract Factory design pattern can also be used to create cross-platform UIs without coupling the client code to concrete UI classes and keeping all created views consistent with different operating systems. That will be covered in future articles. Until then, Happy Coding!

Similar Posts