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
105: The Abstract Factory Pattern
I've seen this happen in almost every mid-sized project I've joined. A developer starts with a simple factory to handle object creation, and for a while, it works great. But then the requirements grow, and suddenly that factory becomes a bloated "God Class" that everyone is afraid to touch. Let's look at a scenario where this usually falls apart.
The Switch-Statement Nightmare
Imagine you're building a game with different biomes: Forest and Desert. Each biome needs its own set of objects—like a Tree and a Creature. At first, you might write a factory like this:
public class GameElementFactory
{
public ITree CreateTree(string biomeType)
{
return biomeType switch
{
"Forest" => new ForestTree(),
"Desert" => new DesertTree(),
_ => throw new ArgumentException("Invalid biome")
};
}
public ICreature CreateCreature(string biomeType)
{
return biomeType switch
{
"Forest" => new ForestCreature(),
"Desert" => new DesertCreature(),
_ => throw new ArgumentException("Invalid biome")
};
}
}
On the surface, this looks fine. But here is where the "bug" manifests—not as a compiler error, but as a maintenance disaster. First, there's no way to guarantee consistency. A developer could accidentally call CreateTree("Forest") and then CreateCreature("Desert") in the same scene, creating a weird hybrid world that breaks your game logic. Second, every time you add a new biome (like "Tundra" or "Volcano"), you have to open this class and modify every single method. You're violating the Open/Closed Principle in the worst way possible.
Grouping Families with Abstract Factories
The fix isn't just to add more logic to the existing factory; it's to change the level of abstraction. Instead of one factory that knows about every single object in the game, we create a factory that knows how to create a family of related objects.
First, we define the interface for the factory itself. This interface doesn't care about "Forest" or "Desert"; it only cares that any biome factory must be able to produce a tree and a creature:
public interface IBiomeFactory
{
ITree CreateTree();
ICreature CreateCreature();
}
Now, we implement concrete factories for each biome. I like this approach because the logic for "Forest" is now entirely encapsulated in one place. If you need to change how a Forest creature is instantiated, you don't have to scroll through a 500-line switch statement in a global factory class.
public class ForestBiomeFactory : IBiomeFactory
{
public ITree CreateTree() => new ForestTree();
public ICreature CreateCreature() => new ForestCreature();
}
public class DesertBiomeFactory : IBiomeFactory
{
public ITree CreateTree() => new DesertTree();
public ICreature CreateCreature() => new DesertCreature();
}
Enforcing Consistency in the Client
The real magic happens when you use these factories in your game engine. Your engine no longer asks for a "Forest" tree; it just asks the current factory to give it a tree. It doesn't even need to know which biome it's currently in.
public class GameWorld
{
private readonly ITree _tree;
private readonly ICreature _creature;
// The world doesn't know if it's Forest or Desert.
// It just knows it has a factory that provides the right parts.
public GameWorld(IBiomeFactory factory)
{
_tree = factory.CreateTree();
_creature = factory.CreateCreature();
}
}
By passing the IBiomeFactory into the GameWorld, you've completely eliminated the possibility of mixing objects from different biomes. If you pass in a DesertBiomeFactory, the world is guaranteed to be consistently "Desert." If you want to add a "Tundra" biome tomorrow, you just create a TundraBiomeFactory and a couple of Tundra objects. You don't have to change a single line of existing code in your GameWorld or your other factories. That's the power of the Abstract Factory pattern: it moves the decision of which family to use to the very top level of your application, leaving the rest of your code clean and agnostic.
📋 Practical Task
Implementing a Cross-Platform UI Theme Engine
You are building a dashboard that must support two different visual styles: Light Mode and Dark Mode. Each mode requires a specific set of UI components: a Button and a TextBox.
Your task is to implement the Abstract Factory pattern to ensure that the dashboard never accidentally mixes Light Mode buttons with Dark Mode textboxes.
- Create interfaces for
IButtonandITextBox. - Implement concrete classes for
LightButton,LightTextBox,DarkButton, andDarkTextBox. - Define an
IThemeFactoryinterface with methods to create buttons and textboxes. - Implement
LightThemeFactoryandDarkThemeFactory. - Create a
Dashboardclass that accepts anIThemeFactoryin its constructor and uses it to initialize its components.
Validation: In your Main method, instantiate a Dashboard using a DarkThemeFactory and verify that both the button and the textbox produced are the "Dark" versions.
There are no comments for now.