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.
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.
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.
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
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.
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 introduces a lot of new abstract classes or interfaces to implement this pattern which increases complexity.
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.