C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
104: The Factory Pattern
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
INPCinterface with a methodvoid Interact(). - Create three concrete classes:
Villager,Guard, andMerchant, each implementingInteract()with a unique message (e.g., "Hello traveler!", "Halt! Who goes there?", "Looking for a bargain?"). - Implement an
NPCFactoryclass with a methodCreateNPC(string type)that returns the appropriateINPCimplementation. - In your
Mainmethod, simulate spawning one of each NPC using the factory and calling theirInteract()method.
There are no comments for now.