Abstract Factory – Design Patterns in Python

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.
Here at LinuxAPT, you can learn some Python design patterns.

Problem for Design Patterns in Python

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.

What is the 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.

Here, 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.
Now let's look at the code in question.

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

 

How to apply Abstract Factory design pattern ?

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.

It's Pros:

1. It decouples concrete products. From the client code.
2. It ensures that the products getting from a factory are compatible with each other.    
3. 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.    
4. It follows the Open/Closed Principle. You can easily extend the application for new variants of products without breaking the existing code.

It's Cons:

It introduces a lot of new abstract classes or interfaces to implement this pattern which increases complexity.

[Need to fix any Linux distribution related errors ? We can help you. ]

This article covers Abstract Factory design pattern in Python.

Basically, 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.

Abstract Factory is a creational design pattern, which solves the problem of creating entire product families without specifying their concrete classes.

Abstract Factory Method is a Creational Design pattern that allows you to produce the families of related objects without specifying their concrete classes.
Using the abstract factory method, we have the easiest ways to produce a similar type of many objects.
It provides a way to encapsulate a group of individual factories.

Advantages of using Abstract Factory method:
This pattern is particularly useful when the client doesn’t know exactly what type to create.
1. It is easy to introduce the new variants of the products without breaking the existing client code.
2. Products which we are getting from factory are surely compatible with each other.

Disadvantages of using Abstract Factory method:
1. Our simple code may become complicated due to the existence of lot of classes.
2. We end up with huge number of small fies i.e, cluttering of files.

Examples of Factory pattern in Python:
1. With the Factory pattern, you produce instances of implementations (Apple, Banana, Cherry, etc.) of a particular interface -- say, IFruit.
2. With the Abstract Factory pattern, you provide a way for anyone to provide their own factory. This allows your warehouse to be either an IFruitFactory or an IJuiceFactory, without requiring your warehouse to know anything about fruits or juices.

Related Posts