Loading episodes…
0:00 0:00

The Simple Factory Pattern: A Visual Guide to Cleaner Code

00:00
BACK TO HOME

The Simple Factory Pattern: A Visual Guide to Cleaner Code

10xTeam December 11, 2025 7 min read

The Simple Factory is a creational design pattern that provides a centralized interface for creating objects in a superclass, letting subclasses alter the type of objects that will be created. While not one of the original “Gang of Four” patterns, it’s a widely used and practical first step towards decoupling your client code from concrete class implementations.

This guide will visually break down why, when, and how to use the Simple Factory to make your code cleaner and more maintainable.

The Problem: Scattered Creation Logic

Imagine you’re building a logistics system. Your application needs to handle different modes of transport, like trucks and ships.

First, let’s define a common interface for all transport types:

// --- ITransport.cs ---
public interface ITransport
{
    string Deliver();
}

Next, we create concrete implementations for each transport method:

// --- Ship.cs ---
public class Ship : ITransport
{
    public string Deliver() => "Goods are being delivered by sea.";
}

// --- Truck.cs ---
public class Truck : ITransport
{
    public string Deliver() => "Goods are being delivered by road.";
}

Now, in your client code (the part of the app that uses these classes), you need to decide which object to create based on some input. The most straightforward approach is using a conditional block:

public void DispatchShipment(string transportType)
{
    ITransport transport;

    if (transportType == "ship")
    {
        transport = new Ship();
    }
    else if (transportType == "truck")
    {
        transport = new Truck();
    }
    else
    {
        throw new ArgumentException("Invalid transport type");
    }

    Console.WriteLine(transport.Deliver());
}

This works, but it has a major flaw. What happens when the business decides to add air freight?

The Pain of Scaling

To add a new Airplane transport, you must:

  1. Create the Airplane class.
  2. Modify the conditional logic in every single place where you create transport objects.

Here’s the new class:

// --- Airplane.cs ---
public class Airplane : ITransport
{
    public string Deliver() => "Goods are being delivered by air.";
}

And here’s the change you have to make to your client code. This is where the problem becomes obvious:

  public void DispatchShipment(string transportType)
  {
      ITransport transport;
  
      if (transportType == "ship")
      {
          transport = new Ship();
      }
      else if (transportType == "truck")
      {
          transport = new Truck();
      }
+     else if (transportType == "airplane")
+     {
+         transport = new Airplane();
+     }
      else
      {
          throw new ArgumentException("Invalid transport type");
      }
  
      Console.WriteLine(transport.Deliver());
  }

If this logic is repeated in 5, 10, or 20 places across your application, you have to hunt them all down and update them. This is tedious, error-prone, and violates the Single Responsibility Principle (SRP). The client code shouldn’t be responsible for the “how” of object creation.

The Solution: Centralizing with a Simple Factory

A Simple Factory encapsulates the creation logic into a single, dedicated class. Its only job is to create and return an object of the correct type.

Let’s visualize the shift in architecture.

Before: Decentralized Chaos

graph TD;
    subgraph Client Code
        A[Dispatch Logic]
    end

    subgraph Creation Logic
        B{Input == 'truck'?};
        C{Input == 'ship'?};
        D{Input == 'airplane'?};
    end

    subgraph Concrete Classes
        E[new Truck()];
        F[new Ship()];
        G[new Airplane()];
    end

    A --> B;
    A --> C;
    A --> D;

    B -- Yes --> E;
    C -- Yes --> F;
    D -- Yes --> G;

After: Centralized Order

graph TD;
    A[Client Code] --> B[TransportFactory];
    subgraph Factory
        B -- Creates --> C[Truck];
        B -- Creates --> D[Ship];
        B -- Creates --> E[Airplane];
    end

Implementing the Factory

Let’s create a TransportFactory class. We’ll add a static method CreateTransport that takes the type as input and returns the corresponding object.

// --- TransportFactory.cs ---
public static class TransportFactory
{
    public static ITransport CreateTransport(string transportType)
    {
        return transportType.ToLower() switch
        {
            "truck" => new Truck(),
            "ship" => new Ship(),
            "airplane" => new Airplane(),
            _ => throw new NotSupportedException($"{transportType} is not a supported transport method.")
        };
    }
}

[!TIP] Using a C# 9 switch expression makes the factory code more concise and readable than a traditional switch statement.

Refactoring the Client Code

Now, we can refactor our client code to use the factory. The difference is dramatic.

  public void DispatchShipment(string transportType)
  {
-     ITransport transport;
- 
-     if (transportType == "ship")
-     {
-         transport = new Ship();
-     }
-     else if (transportType == "truck")
-     {
-         transport = new Truck();
-     }
-     else if (transportType == "airplane")
-     {
-         transport = new Airplane();
-     }
-     else
-     {
-         throw new ArgumentException("Invalid transport type");
-     }
+     ITransport transport = TransportFactory.CreateTransport(transportType);
  
      Console.WriteLine(transport.Deliver());
  }

The client is now completely decoupled from the concrete implementations. It doesn’t know or care how the ITransport object is created; it only knows that it can get one from the factory.

If we need to add a Drone transport method tomorrow, we only have to modify the TransportFactory. None of the client code needs to change.

Organizing your files logically makes the pattern even clearer.

LogisticsApp/
├── Transports/
│   ├── ITransport.cs
│   ├── Truck.cs
│   ├── Ship.cs
│   └── Airplane.cs
├── Factories/
│   └── TransportFactory.cs
└── Program.cs

Best Practices & Considerations

[!WARNING] Open/Closed Principle: The Simple Factory itself violates the Open/Closed Principle. The principle states that software entities should be open for extension but closed for modification. Every time you add a new transport type, you must modify the factory class. More advanced patterns like the Factory Method or Abstract Factory solve this issue, but come with added complexity.

Quiz: Test Your Understanding

Question: If your application only creates transport objects in a single location, is a Simple Factory still a good idea?

Answer: Not necessarily. As the original transcript mentions, if the creation logic exists in only one place and is unlikely to be duplicated, the overhead of creating a factory might be unnecessary. The primary benefit of a Simple Factory is to reduce duplication and centralize logic that is scattered across an application.

Static vs. Instance Factories

The example uses a static factory for simplicity. However, in larger applications, you might prefer an instance-based factory that can be registered with a Dependency Injection container. This makes your code more testable, as you can easily mock the factory in your unit tests.

Conclusion

The Simple Factory pattern is a powerful tool for cleaning up conditional object creation. It centralizes logic, reduces code duplication, and makes your system significantly easier to maintain and extend.

Key Takeaways:

  • Use it when: You have multiple if/else or switch blocks creating instances of related classes scattered across your codebase.
  • Benefit: It decouples client code from concrete classes and centralizes creation logic.
  • Next Steps: When the factory itself becomes a maintenance bottleneck, it’s a sign you may need to graduate to a more flexible pattern like the Factory Method.

Join the 10xdev Community

Subscribe and get 8+ free PDFs that contain detailed roadmaps with recommended learning periods for each programming language or field, along with links to free resources such as books, YouTube tutorials, and courses with certificates.

Audio Interrupted

We lost the audio stream. Retry with shorter sentences?