Back to basics: Factory Pattern

It's interesting how we sometimes forget simple things because we don't use them as often. As developers, we tend to rely on high-level abstractions that handle the difficult tasks for us, allowing us to focus on other tasks.

Recently, while working with my team, I realized that we were handling a use case for the factory pattern in a very rudimentary way. This made me realize that not everyone may have the same background knowledge and I decided to teach them this pattern so they could use it to implement the solution to the problem at hand.

The Factory Pattern involves creating objects by delegating the work to the appropriate sub-class. This separates the logic that would otherwise be in the main class or abstraction, making it easier to manage. Objects created using this pattern should be similar in type or belong to a hierarchy.

The Factory Method abstracts the process of object creation by providing an interface to return the desired object. This allows you to specify the object you want at runtime, rather than creating it in a more traditional manner.

UML Class Diagram

The Factory interfaces and concrete factory objects represent the process of creating a new pet. The Factory interfaces specify the methods for creating a new pet, while the concrete factory objects are responsible for actually creating the pets. This allows for flexibility in the creation process, as different factories can be created for different pet lines, such as different breeds of dogs or different types of birds.

Implementation

Here, we will define the interface that will serve as a blueprint for our implementations.

interface Pet {
 getName(): string;
 getAge(): number;
 getLastMeal(): string;
}

Next, we can directly implement our interface into the Dog and Cat classes.

class Dog implements Pet {
  getName(): string {
    return "duke";
  }
  
  getAge(): number {
    return 4;
  }

  getLastMeal(): string {
    return "kibble"
  }
}

class Cat implements Pet {
  getName(): string {
    return "git";
  }
  
  getAge(): number {
    return 2;
  }

  getLastMeal(): string {
    return "fish"
  }
}

With the Factory Method pattern, it is desired to avoid instantiating them using the new operator, and instead, a factory is defined for each type of Pet.

interface PetFactory {
  create(): Pet;
}

class DogFactory implements PetFactory {
  create(): Pet {
    return new Dog();
  }
}

class CatFactory implements PetFactory {
   create(): Pet {
    return new Cat();
  }
}

By utilizing the Factory Method, you can ensure that the objects are only created once throughout the lifespan of the program. You can then pass these objects whenever the PetFactory interface is required, keeping the object creation logic centralized and unchanging.

const dogFactory = new DogFactory();
const catFactory = new CatFactory();
const factories: PetFactory[] = [dogFactory, catFactory, dogFactory];
for(let i = 0; i < factories.length; i++){
  factories[i].create()
}

This is a classic implementation of a factory design pattern, however, I don't have a strong preference for creating abstractions to handle small tasks. Instead, I prefer to use parameters to determine which object to instantiate, rather than relying on an interface.

const enum PetType {
  DOG,
  CAT
}

class PetCreator {
  create(petType: PetType): Pet {
    switch (petType) {
      case PetType.DOG:
        return new Dog();
        break;
      case PetType.Cat
        return new Cat();
        break;
    }
  }
}

This solution might seem okay for now, especially if you're still building the app. But as you add more types of objects, it'll quickly become a pain to keep updating the PetType and switch cases. Trust me, you'll want to rethink this code and use a Factory method interface in the future

In conclusion, the Factory Pattern is a powerful tool for creating objects in a clean and organized way. By encapsulating the object creation logic, it makes your code more maintainable and scalable. Whether you're just starting out with object-oriented programming or you're an experienced developer, it's definitely worth taking the time to understand and implement this pattern in your projects. Happy coding!