Skip to Content
Course content

104: The Factory Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was lead on a project for a logistics company that handled different types of shipping carriers. In the beginning, it was simple: we only used FedEx. Throughout the codebase, you'd see var shipper = new FedExShipper(); everywhere. It worked great for a month. Then the business decided to add UPS, then DHL, and then a local courier service for last-mile deliveries.

Suddenly, my codebase was littered with if (carrier == "FedEx") { ... } else if (carrier == "UPS") { ... } blocks. Every time we added a new carrier, I had to hunt down every single place where a shipper was instantiated and add another else-if. It was tedious, error-prone, and frankly, a bit embarrassing for a professional project. That's when I realized I had a tight coupling problem, and the Factory Pattern was the cure.

Breaking the Dependency on Concrete Classes

The core problem in that logistics app wasn't the logic of shipping—it was the instantiation. When you use the new keyword, you are hard-coding a dependency on a specific class. You're telling your program, "I don't just want something that can ship a package; I want specifically a FedExShipper."

The Factory Pattern solves this by moving the object creation logic into a separate class. Instead of the client code deciding which class to instantiate, it asks a "Factory" to provide an object that conforms to a specific interface. The client doesn't know—or care—which concrete class it gets back, as long as it can call the methods defined in the interface.

Implementing a Document Export Factory

Let's look at a cleaner example. Imagine you're building a report generator that needs to export data to PDF, JSON, or XML. You don't want your UI logic to be cluttered with the details of how to create these exporters.

public interface IDocumentExporter
{
    void Export(string data);
}

public class PdfExporter : IDocumentExporter
{
    public void Export(string data) => Console.WriteLine($"Exporting {data} to PDF format.");
}

public class JsonExporter : IDocumentExporter
{
    public void Export(string data) => Console.WriteLine($"Exporting {data} to JSON format.");
}

public class XmlExporter : IDocumentExporter
{
    public void Export(string data) => Console.WriteLine($"Exporting {data} to XML format.");
}

public static class ExporterFactory
{
    public static IDocumentExporter CreateExporter(string format)
    {
        return format.ToLower() switch
        {
            "pdf" => new PdfExporter(),
            "json" => new JsonExporter(),
            "xml" => new XmlExporter(),
            _ => throw new NotSupportedException($"Format {format} is not supported.")
        };
    }
}

Now, look at how much cleaner the calling code becomes. I no longer have to worry about which concrete class to use; I just tell the factory what I need.

string userPreference = "pdf"; // This would come from a config or UI
IDocumentExporter exporter = ExporterFactory.CreateExporter(userPreference);
exporter.Export("Monthly Financial Report");

Avoiding the Over-Engineering Trap

I've seen a lot of developers start using factories for everything, and that's a mistake. If you have a class that will never have a second implementation, creating a factory for it is just adding unnecessary boilerplate. You're adding a layer of abstraction that provides no value.

Use the Factory Pattern when you have a family of related classes that implement the same interface, and the specific class you need is determined at runtime. It's about centralizing the "decision" of which object to create. If you ever find yourself writing the same switch or if/else block in three different places to decide which class to new up, that is your signal to build a factory.




📋 Practical Task

Exercise: The Game NPC Spawner

You are building a simple RPG engine. The game needs to spawn different types of Non-Player Characters (NPCs) based on the region the player is entering. You need to implement a Factory Pattern to handle this so the GameWorld class doesn't need to know the details of every NPC type.

Requirements:

  • Create an INPC interface with a method void Interact().
  • Create three concrete classes: Villager, Guard, and Merchant, each implementing Interact() with a unique message (e.g., "Hello traveler!", "Halt! Who goes there?", "Looking for a bargain?").
  • Implement an NPCFactory class with a method CreateNPC(string type) that returns the appropriate INPC implementation.
  • In your Main method, simulate spawning one of each NPC using the factory and calling their Interact() method.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.