Factory Design Pattern in Python
Tags: oop, python, designpatterns, cleancode
Published: 2023-01-21 14:07:50

Factory pattern is a **creational design pattern**. As the name suggests, a "factory" is a place where things are created.


The factory pattern defines an interface for creating an object, but it lets the subclasses define which object to build; it does this through a factory method.


**Scenario:** We want to support several models of cars and their functionalities, like start and stop. But we don’t know which one we will need until runtime.


A brute force approach to implementing this would be: at runtime, when we get the name of the car model, we can write if-else statements or even switch case statements to decide which class object to create and call.

```python
from cars.ford import Ford
from cars.ferrari import Ferrari
from cars.generic import car

def get_car(car_name):
  if car_name == 'Ford':
    return Ford()
  elif car_name == 'Ferrari':
    return Ferrari()
  else:
    return GenericCar()

for car_name in ['Jeep', 'Ferrari', 'Tesla']:
  car = get_car(car_name)
  car.start()
  car.stop()
```

This code snippet works perfectly, but if we want to support more car models, we will have to modify the above implementation and add more class import statements, which breaks the [open/ closed principle](https://dev.to/khushboo/back-to-basics-solid-principles-part-2-f3f).

Also, we are directly instantiating the car classes, which is breaking the [dependency-inversion principle](https://dev.to/khushboo/back-to-basics-solid-principles-dependency-inversion-3fp3) as we are depending on the implementation of these classes.

Now let's take a look at how we can refactor our code to avoid breaking the SOLID principles and still be able to extend our application easily.


![Class diagram showing factory design pattern](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cp40ey072gjmuvzvanwo.png)Class diagram – factory pattern

In the class diagram above, we see an `AbsCars` class, which is an abstract class that says we must implement start and stop methods in all its concrete classes, and in the bottom, we have three concrete classes; these are the car classes that we want to support: Jeep, Ford, and Ferrari, which implement the AbsCars class.

In the end, we have the `CarFactory` class, which creates the instance of the desired car class.

The AbsCars class will look like this:

```python
import abc

class AbsCars(abc.ABC):
  @abc.abstractmethod
  def start(self):
    pass

  @abc.abstractmethod
  def stop(self):
    pass
```

The start and stop methods are declared as abstract methods, which basically means all the concrete classes implementing the AbsCars class need to implement them as well.

For example, the `Ford` class:

```python
from cars.abscars import AbsCars

class Ford(AbsCars):
  def start(self):
    if is_fuel_present:
      print("ford engine is now running")
      return
    print("Low on fuel")

  def stop(self):
    print("Ford engine shutting down")
```

Other car classes are implemented in the same way. The factory pattern allows all the concrete classes to have their own implementations of these abstract methods, which may or may not be the same.

For example our `GenericCar` class can be implement like this:

```python
from cars.abscars import AbsCars

class GenericCar(AbsCars):
  def __init__(self, car_name):
    self.name = car_name

  def start(self):
    print(f"{self.name} engine starting!")

  def stop(self):
    print(f"{self.name} engine stopping!")
```

Now our `CarFactory` class

```python
from inspect import getmembers, isclass, isabstract
import cars

class CarFactory():
  cars = {}

  def __init__(self):
    self.load_cars()

  def load_cars(self):
    classes = getmembers(cars, lambda m: isclass(m) and not isabstract(m))
    for name, _type in classes:
      if isclass(_type) and issubclass(_type, autos.AbsCars):
        self.cars.update([[name, _type]])

  def create_instance(self, car_name):
    if car_name in self.cars:
      return self.cars[car_name]()
    else:
      return cars.GenericCar(car_name)
```

`Cars` dictionary here will keep a reference to the class against the name of the car model.

The init dunder method will call `load_cars()` which builds the `cars` dictionary.

The `load_cars` method will get all the classes in the cars package that are not abstract classes, and then it will look for classes that are subclasses of our AbsCars class and add them to the `cars` dictionary.

The `create_instance` function looks for the car name in the `cars` dictionary and, if found, returns a new instance of the class; otherwise, it returns an instance of the GenericCars class.

Lastly, in our main module, we just need to import and instantiate the AutoCars class, then loop through the car names, calling the create_instance method for each car.

```python
from cars.carfactory import CarFactory

factory = CarFactory()

for car_name in ['Ford', 'Ferrari', 'Tesla']:
  car = factory.create_instance(car_name)
  car.start()
  car.stop()
```

With this approach we can keep on adding support for more car models by just adding the car classes implementing the AbsCar class and not touch any other code in our project!