Skip to content

Abstract Factory

The ʻAbstract Factorypattern extends the pattern [Factory Method](factory_method.md) and adds an extra level of abstraction to it. LikeFactory 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.