Design Patterns in Python : Factory method
Tags: python, designpatterns, oop, lowleveldesign
Published: 2023-07-28 07:12:55

Design Patterns represent an idea, not a particular implementation. By using it, you can make your code more flexible, maintainable, reusable and scalable.

Design Patterns are reusable programming solutions that have been used in various real-life contexts. There are several categories in Object-Oriented Programming, depending on the type of problem it addresses and the type of solution it helps to build.

They are split into 3 categories :

1. Creational
       - Deals with different aspects of object creation.

2. Structural
       - Proposes a way of composing objects for creating new functionality

3. Behavioral
       - Deals with the communication or interaction of objects

Am going to discuss one of the creational design patterns here which is the Factory method.

I have tried to make this as simple as possible so that the novice programmers will also be able to understand what actually are design patterns and why there are used or recommended.

Lets get started.

## Introduction

It is used for providing convenient and better alternatives for object creation.

In this factory design pattern, the client (means the client code) asks for object creation without knowing where the actual object creation is happening or where is it coming from or which class is used to generate that object.

This makes it easier to track the objects getting created instead of making the client take care of the direct class instantiation process ( directly creating the object from the class).

This design pattern helps us in decoupling or separating the logic of object creation from the logic that uses it. 

## When to use it

1. If you realise that you cannot track the objects created by the application because there are too many objects created at different places, you should consider using factory method.

2. Most useful use case is defining a method which returns the database instance creation based on the type of database used.

3. Whenever there is a change happening, we can change it in a single function and there is no need to change the code which uses that object.

4. One more important use case is the improvement in the memory allocation and its performance.
   At every object creation, memory is allocated. If the object is not used somehow for some time, the allocated memory is just a wasteful allocation. Also, extra memory allocation happens when any new object is created.

Below is a small illustration of the above use case.

```
class A:
    ...


if __name__ == "__main__":
    a = A()
    b = A()
    print(id(a), id(b))
```
which gives the below output. It shows 2 different memory allocation happened.

![Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pagrectrbryw9s5dltjm.png)

Even if we need the object "a" to execute some actions, "b" will still be created and allocated memory; which is not convenient.

## Implementing factory pattern

I have tried to illustrate the factory pattern using a really simple program. You can try to refactor your existing code or write a new one taking reference to this example.

Okay, so lets create an abstract class first which will keep the program consistent.

```
from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def make_sound(self):
        pass
```

Now, lets create two classes which can inherit the Animal properties.

```
class Dog(Animal):
    def __init__(self):
        self.name = "Mike"

    def __str__(self):
        return self.name

    @property
    def make_sound(self):
        return "bow wow"

class Cat(Animal):
    def __init__(self):
        self.name = "Alexa"

    def __str__(self):
        return self.name

    @property
    def make_sound(self):
        return "meow"
```

We will now create a factory method which returns an instance of either Cat class or a Dog class depending upon the animal type we give as an argument to the function.

```
def animal_invoke_factory(animal: str) -> Animal:
    animal_type = animal.lower().strip()

    if animal_type == "cat":
        result = Cat
    elif animal_type == "dog":
        result = Dog
    else:
        raise ValueError(f"Unable to find {animal}")

    return result()
```

Am creating a wrapper function around the animal_invoke_factory() function. It also handles the exception raised by the animal_invoke_factory() function.

```
def get_animal(animal: str) -> Animal:
    factory_obj = None

    try:
        factory_obj = animal_invoke_factory(animal)
    except ValueError as error:
        print(error)

    return factory_obj
```

Below main() function calls the get_animal() function with the appropriate parameters.

```
def main():
    animal = get_animal("cat")
    print(f"{animal} says {animal.make_sound}")
```

### Code Repo
Please find the entire code here.
[Factory Design Pattern](https://github.com/Anu1996rag/design_patterns/tree/main/factory)


That's it ! 
We are able to implement the factory pattern in a most simple way possible.

I have tried to make it simple to understand and visualize the concept. 
Please let me know your reviews/feedbacks or any suggestions you have.

Happy Learning !!!