Abstract Factory¶
The ʻAbstract Factorypattern extends the pattern [Factory Method](factory_method.md) and adds an extra level of abstraction to it. Like
Factory Method`, this pattern is used to create objects of a family (i.e. implementing a specific interface or inheriting from a specific class). The difference is that we are able to divide them into certain groups among the family of objects. Then, based on these groups and the choice of, for example, the end user of our application, we are able to create a factory producing objects of a certain family belonging to a certain group. This means that there are multiple factories that create objects of the same type.
This pattern is best understood with an example. The following example shows how to build objects that extend the Car
interface. In the example we find 6 implementations of this interface:
AudiA4Wagon
AudiA4Sedan
AudiA4Hatchback
ToyotaCorollaWagon
ToyotaCorollaSedan
ToyotaCorollaHatchback
The first 3 implementations will go to the AudiA4
group, the next three will go to the ToyotaCorolla
group.
The factory (CarFactory
interface) will be able to produce an object of typeCar
based on its body: Sedan, Wagon or Hatchback. The example includes two implementations of such factories. These are the classes ToyotaCorollaFactory
and AudiA4Factory
.
The goal is to give the user a choice of a specific factory implementation. This is done in the FactoryProvider
class, which does it based on the input type. Due to the fact that all classes representing cars implement the Car
interface and all factories implement the CarFactory
interface, we can see that the use of these classes can be based entirely on abstractions.
Common interface for manufactured objects:
class Car:
def get_type(self):
pass
def get_model_name(self):
pass
def get_cylinders_num(self):
pass
def get_producer(self):
pass
def get_engine_volume(self):
pass
def get_trunk_size(self):
pass
class AbstractCar(Car):
def __str__(self):
return (f"Car: {self.get_producer()} {self.get_model_name()} {self.get_type()} has {self.get_cylinders_num()} "
f"cylinders and engine {self.get_engine_volume()} and trunk size {self.get_trunk_size()} litres")
Base class of all Toyota Corolla cars:
class ToyotaCorolla(AbstractCar):
def get_model_name(self):
return "Corolla"
def get_producer(self):
return "Toyota"
Implementations of the ToyotaCorolla
classes, the first group of objects of theCar
family:
class ToyotaCorollaWagon(ToyotaCorolla):
def get_type(self):
return "Wagon"
def get_cylinders_num(self):
return 4
def get_engine_volume(self):
return 1.6
def get_trunk_size(self):
return 540
class ToyotaCorollaHatchback(ToyotaCorolla):
def get_type(self):
return "Hatchback"
def get_cylinders_num(self):
return 4
def get_engine_volume(self):
return 2.0
def get_trunk_size(self):
return 350
class ToyotaCorollaSedan(ToyotaCorolla):
def get_type(self):
return "Sedan"
def get_cylinders_num(self):
return 4
def get_engine_volume(self):
return 1.8
def get_trunk_size(self):
return 420
Base class of all Audi A4 cars:
class AudiA4(AbstractCar):
def get_model_name(self):
return "A4"
def get_producer(self):
return "Audi"
Implementations of the AudiA4
classes, the second group of Car
family objects:
class AudiA4Wagon(AudiA4):
def get_type(self):
return "Wagon"
def get_cylinders_num(self):
return 4
def get_engine_volume(self):
return 1.8
def get_trunk_size(self):
return 520
class AudiA4Hatchback(AudiA4):
def get_type(self):
return "Hatchback"
def get_cylinders_num(self):
return 6
def get_engine_volume(self):
return 2.4
def get_trunk_size(self):
return 300
class AudiA4Sedan(AudiA4):
def get_type(self):
return "Sedan"
def get_cylinders_num(self):
return 4
def get_engine_volume(self):
return 2.0
def get_trunk_size(self):
return 450
An abstraction representing the common interface of all factories:
class CarFactory:
def create_Wagon(self):
pass
def create_hatchback(self):
pass
def create_sedan(self):
pass
Factory implementations:
class ToyotaCorollaFactory(CarFactory):
def create_Wagon(self):
return ToyotaCorollaWagon()
def create_hatchback(self):
return ToyotaCorollaHatchback()
def create_sedan(self):
return ToyotaCorollaSedan()
class AudiA4Factory(CarFactory):
def create_Wagon(self):
return AudiA4Wagon()
def create_hatchback(self):
return AudiA4Hatchback()
def create_sedan(self):
return AudiA4Sedan()
An additional level of abstraction in the described pattern, allowing the selection of a specific factory:
class FactoryProvider:
@staticmethod
def create_factory(factory_type):
if factory_type == 'T':
return ToyotaCorollaFactory()
elif factory_type == 'A':
return AudiA4Factory()
else:
return None
Exemplary use of the pattern:
def main():
factory_type = input('What car do you want to produce - choose A or T: ')
factory = FactoryProvider.create_factory(factory_type)
if factory:
hatchback = factory.create_hatchback()
print(hatchback)
if __name__ == '__main__':
main()
As you can see, the main advantage of the ʻAbstract Factory` design pattern is the ability to work with abstractions, not breaking the rules of SOLID and hiding implementation details. The problem, in turn, may be a large number of classes and interfaces that we have to implement to use it.