Boost Your Code and Efficiency with the Factory Pattern

Admir Mujkic
8 min readMar 13

--

The article emphasizes the importance of creating a stable and easily maintainable application that meets future needs. It discusses the use of factories to create a loosely coupled system and separate code that can vary from the remaining part of the codebase. The article also highlights the need for analysis and refactoring of code to improve the quality of the application.

The image is taken from https://www.successfactor.co.nz

Where is the problem?

Let’s say you’re making a program about tigers and cats. You can’t create the animal object within the client code. Why? One reason is that you want to hide the process of creating the object from the client. This is because changes might happen in the future that will require the client code to be updated and tested again.

Another reason is that other classes might create cats or tigers, so it’s better to put the code that creates them in a shared place. This way, multiple clients can use it. In the upcoming demonstrations, we’ll learn how to separate the process of creating the object from the client code.

Examples

Before looking at the complete program in demo one, there are some important points to consider.

  • The AnimalFactory class in this program is responsible for creating an object and has a method called CreateAnimal() to create either a tiger or a cat instance.
  • The CreateAnimal() method in this program is currently nonstatic, but it can be made static.
  • In this example, the AnimalFactory class functions as a factory class by containing a method called CreateAnimal() to create instances. So, the CreateAnimal() method is the factory method in this program.
  • To get an animal and display its behavior, the client code instantiates the factory class. This is why the client code uses the following code:
AnimalFactory animalFactory = new();
var animal = animalFactory.CreateAnimal(animalType: "cat");
animal.DisplayBehavior();

Let’s start with the first demo

Let’s start with the IAnimal interface, which ensures that each animal has a DisplayBehavior() method. It makes it easier to work with different animal types by treating them as instances of IAnimal.

/// <summary>
/// The Animal interface declares operations common to all supported animals.
/// </summary>
public interface IAnimal
{
void DisplayBehavior();
}

Cat and Tiger implement the IAnimal interface, which requires them to implement DisplayBehavior() specifically. Meowing is the behavior of a cat, and growling is the behavior of a tiger.

By implementing the IAnimal interface, both classes become instances of the interface, making it easy to work with them. This is shown in the Main() method of the Program class, where we create Cat and Tiger objects using the AnimalFactory class and then call their DisplayBehavior() methods.

New animal types can be added just by implementing the IAnimal interface and providing a specific DisplayBehavior() implementation, without modifying anything.

/// <summary>
/// The Cat class implements the IAnimal interface.
/// </summary>
internal class Cat : IAnimal
{
public Cat()
{
Console.WriteLine(value: "A cat is created.");
}

public void DisplayBehavior()
{
Console.WriteLine(value: "It meows.It loves to stay at a home. ");
}
}
/// <summary>
/// The Tiger class implements the IAnimal interface.
/// </summary>
internal class Tiger : IAnimal
{
public Tiger()
{
Console.WriteLine(value: "A tiger is created.");
}

public void DisplayBehavior()
{
Console.WriteLine(value: "It roars. It loves to roam in the jungle.");
}
}

Through its CreateAnimal() method, the AnimalFactory class creates instances of different animal types. It takes a string parameter animalType and returns an IAnimal object of that type. By using a switch statement, it determines which type of animal to create based on the animalType parameter.

It’s easy to create the right type of animal object with the AnimalFactory class. In the CreateAnimal() method, new animal types can be added by adding a new case to the switch statement. This makes it easier to manage and maintain the code by centralizing the creation of animal objects.

/// <summary>
/// The AnimalFactory class is a factory class that creates animals.
/// </summary>
public class AnimalFactory
{
/// <summary>
/// The CreateAnimal method creates an animal based on the animal type.
/// </summary>
/// <param name="animalType">The type of animal to create.</param>
/// <returns>An object that implements the IAnimal interface.</returns>
/// <exception cref="ArgumentException">Thrown when an invalid animal type is specified.</exception>
public IAnimal CreateAnimal(string animalType)
{
return animalType switch
{
"cat" => new Cat(), // Create a new cat object
"tiger" => new Tiger(), // Create a new tiger object
_ => throw new ArgumentException(message: "Invalid animal type. Only cat or tiger are allowed.") // Throw an exception for invalid input
};
}
}
using AdmirLive.BoostYourCode.Execute.Factory;

namespace AdmirLive.BoostYourCode.Execute;

internal class Program
{
private static void Main(string[] args)
{
AnimalFactory animalFactory = new();

var animal = animalFactory.CreateAnimal(animalType: "cat");
animal.DisplayBehavior();

animal = animalFactory.CreateAnimal(animalType: "tiger");
animal.DisplayBehavior();
}
}

Here is the output:

This is the output of the program.

Let’s take a closer look at this program

Demo 1 uses a common approach in programming. It’s called a simple factory pattern in programming. Let’s take a look at this program.

  • Perhaps this application will need to create another animal type in the future, like a monkey. What can we do? If we want to consider monkeys, we’ll need to modify AnimalFactory. If we do this, we’ll violate the Open-Closed Principle (OCP), so we’ll have to retest AnimalFactory.
  • The Visual Studio or Rider saying that the CreateAnimal() method doesn’t access instance data. Marking methods as static can also improve performance for performance-sensitive code.
  • I won’t take this suggestion now because it’s not a priority for performance. Additionally, a static method has disadvantages, such as being unable to change behavior at runtime.

Follow this link to find this version on GitHub. (Demo 1 :: Boost Your Code and Efficiency with the Next Pattern)

Improved Version

We can make the program better by following the Open-Closed Principle (OCP). You’ll see a new hierarchy in the demo. The comments are there to help you understand the code.

/// <summary>
/// The AnimalFactory class declares the factory method that is supposed to return an object of a Animal class.
/// </summary>
public abstract class AnimalFactory
{
// This is the factory method that is supposed to return an object of a Animal class in the derived classes.
public abstract IAnimal CreateAnimal();
}
/// <summary>
/// The TigerFactory class is a concrete factory class that implements the factory method to return a Tiger object.
/// </summary>
public class TigerFactory : AnimalFactory
{
// This method will return a Tiger object.
public override IAnimal CreateAnimal()
{
return new Tiger();
}
}
/// <summary>
/// The CatFactory class is a concrete factory class that implements the factory method to return a Cat object.
/// </summary>
public class CatFactory : AnimalFactory
{
// This method will return a Cat object.
public override IAnimal CreateAnimal()
{
return new Cat();
}
}

What is the benefit of this?

In the upcoming demonstration, I’ll show how to make the code segment closed for modification. You can create a Monkey class that implements the IAnimal interface and a MonkeyFactory class that implements the AnimalFactory with a new CreateAnimal() method if you need to support a new animal type, like a monkey. You can test only the new classes, leaving the existing code untouched.

I want to highlight two different inheritance hierarchies before the demonstration: one for the animal hierarchy and one for the factory hierarchy.

The factory hierarchy
The animal hierarchy

Let’s start with the second demo

namespace AdmirLive.BoostYourCode.Execute;

internal class Program
{
private static void Main(string[] args)
{
// Create a cat factory based on the AnimalFactory class.
AnimalFactory animalFactory = new CatFactory();

// Create a cat based on the cat factory.
IAnimal animal = animalFactory.CreateAnimal();
animal.DisplayBehavior();

// Create a tiger factory based on the AnimalFactory class.
animalFactory = new TigerFactory();

// Create a tiger based on the tiger factory.
animal = animalFactory.CreateAnimal();
animal.DisplayBehavior();
}
}
This output is the same as the previous output.

The factory was initially implemented with a single AnimalFactory class that created different animal objects based on the input parameter. Because this design required modifying the AnimalFactory class every time a new animal type was added, it violated the OCP.

The AnimalFactory class was refactored to include subclasses for each animal type: CatFactory and TigerFactory. The Cat and Tiger subclasses implement the AnimalFactory interface.

As a result, the AnimalFactory code became closed for modification but open for extension. By creating new subclasses of AnimalFactory, we can add support for new animal types without changing the existing code.

Below is a summary of the modified implementation:

  • Choose a CatFactory or TigerFactory in the client code.
  • Cat and Tiger instances are created by subclasses of AnimalFactory.
  • We’ll support the OCP and make the solution better and more extensible.

Follow this link to find this version on GitHub. (Demo 2 :: Boost Your Code and Efficiency with the Next Pattern)

Finally

Lastly, I would like to emphasize the importance of creating stable, maintainable, and efficient code using patterns like the factory pattern and principles like the Open-Closed Principle.

A factory pattern centralizes the creation of objects, making the code easier to manage. The creation of objects can be separated from the client code, so future changes can be made without affecting the client code.

In addition, the article shows two demo programs that illustrate the use of the simple factory pattern and the improved version using the Open-Closed Principle. The simple factory pattern is easy to understand and implement, but it violates the Open-Closed Principle because you have to modify existing code to add new animal types.

By contrast, the improved version uses subclasses of the factory class to create different animal objects. The code is closed for modification, but open for extension, so you can add new animal types without changing it.

In general, I think developers can save time, reduce errors, and improve the quality of their apps by using these techniques.

The entry code examples can be found on my GitHub account following the link.

P.S. If you believe I can help with something, don’t hesitate to contact me, and I will reach you as soon as possible. admir.m@penzle.com

Cheers! 👋

--

--

Admir Mujkic

Admir combined engineering expertise with business acumen to make a positive impact & share knowledge. Dedicated to educating the next generation of leaders.