Abstract Factory Pattern Real-World Example in C#: Complete Implementation

Abstract Factory Pattern Real-World Example in C#: Complete Implementation

The best way to understand the Abstract Factory pattern is through a complete, real-world example. In this article, we'll build a furniture shop system that demonstrates how Abstract Factory manages families of related objects—exactly the scenario this pattern was designed to solve.

We'll create a system where customers can choose furniture styles (Modern, Victorian, Scandinavian), and the system ensures all furniture pieces match that style. This is a classic Abstract Factory use case: multiple related products (chair, table, sofa) that must come from the same family (style).

By the end of this article, you'll have a complete, working C# implementation that you can run and modify. We'll walk through each component, explain the design decisions, and show how Abstract Factory solves the problem elegantly.

This article focuses on: Narrative application and evolution of Abstract Factory in a real-world scenario. We emphasize why Abstract Factory matters here and how it scales. For step-by-step implementation mechanics, see How to Implement Abstract Factory Pattern. For conceptual foundation, see Abstract Factory Design Pattern: Complete Guide. For decision-making guidance, see When to Use Abstract Factory Pattern.

For foundational knowledge, explore The Big List of Design Patterns for context on all design patterns.

Problem: Furniture Shop System

Imagine you're building a furniture shop application. Customers can choose from different styles:

  • Modern: Sleek, minimalist furniture
  • Victorian: Ornate, classic furniture
  • Scandinavian: Simple, functional furniture

Each style has three types of furniture:

  • Chair: Seating furniture
  • Table: Surface furniture
  • Sofa: Large seating furniture

The challenge: When a customer selects "Modern" style, all furniture must be Modern. You can't mix a Modern chair with a Victorian table—they wouldn't match!

Solution: Abstract Factory Pattern

Abstract Factory is perfect here because we need to ensure all furniture pieces come from the same style family. This example demonstrates why Abstract Factory matters in real-world scenarios and how it scales as requirements evolve. For detailed step-by-step implementation mechanics, see How to Implement Abstract Factory Pattern. Let's implement it step by step.

Step 1: Define Abstract Products

First, we define interfaces for each type of furniture:

// Abstract Product: Chair
public interface IChair
{
    string Style { get; }
    void Sit();
    string GetDescription();
}

// Abstract Product: Table
public interface ITable
{
    string Style { get; }
    void PlaceItems();
    string GetDescription();
}

// Abstract Product: Sofa
public interface ISofa
{
    string Style { get; }
    void Relax();
    string GetDescription();
}

These interfaces define what all furniture pieces can do, regardless of style.

Step 2: Implement Concrete Products

Now we create concrete implementations for each style. Each style family will have its own implementations of the chair, table, and sofa interfaces, ensuring all products within a family share the same aesthetic and behavioral characteristics.

Modern Furniture Family

The Modern furniture family emphasizes minimalist design with clean lines and contemporary materials. Here's how we implement each product type:

// Modern Chair
public class ModernChair : IChair
{
    public string Style => "Modern";
    
    public void Sit()
    {
        Console.WriteLine("Sitting on a sleek modern chair");
    }
    
    public string GetDescription()
    {
        return "Modern chair: Minimalist design with clean lines";
    }
}

// Modern Table
public class ModernTable : ITable
{
    public string Style => "Modern";
    
    public void PlaceItems()
    {
        Console.WriteLine("Placing items on a modern glass table");
    }
    
    public string GetDescription()
    {
        return "Modern table: Glass top with chrome legs";
    }
}

// Modern Sofa
public class ModernSofa : ISofa
{
    public string Style => "Modern";
    
    public void Relax()
    {
        Console.WriteLine("Relaxing on a modern sectional sofa");
    }
    
    public string GetDescription()
    {
        return "Modern sofa: Low profile with geometric patterns";
    }
}

Victorian Furniture Family

The Victorian furniture family features ornate designs with classic elegance. Each product reflects the traditional Victorian aesthetic with rich materials and decorative details: // Victorian Chair public class VictorianChair : IChair { public string Style ⇒ "Victorian";

public void Sit()
{
    Console.WriteLine("Sitting on an ornate Victorian chair");
}

public string GetDescription()
{
    return "Victorian chair: Ornate carvings with velvet upholstery";
}

}

// Victorian Table public class VictorianTable : ITable { public string Style ⇒ "Victorian";

public void PlaceItems()
{
    Console.WriteLine("Placing items on a Victorian mahogany table");
}

public string GetDescription()
{
    return "Victorian table: Dark wood with intricate details";
}

}

// Victorian Sofa public class VictorianSofa : ISofa { public string Style ⇒ "Victorian";

public void Relax()
{
    Console.WriteLine("Relaxing on a luxurious Victorian sofa");
}

public string GetDescription()
{
    return "Victorian sofa: Tufted back with decorative trim";
}

}


### Scandinavian Furniture Family

```csharp
// Scandinavian Chair
public class ScandinavianChair : IChair
{
    public string Style => "Scandinavian";
    
    public void Sit()
    {
        Console.WriteLine("Sitting on a simple Scandinavian chair");
    }
    
    public string GetDescription()
    {
        return "Scandinavian chair: Light wood with simple design";
    }
}

// Scandinavian Table
public class ScandinavianTable : ITable
{
    public string Style => "Scandinavian";
    
    public void PlaceItems()
    {
        Console.WriteLine("Placing items on a functional Scandinavian table");
    }
    
    public string GetDescription()
    {
        return "Scandinavian table: Light birch wood, functional design";
    }
}

// Scandinavian Sofa
public class ScandinavianSofa : ISofa
{
    public string Style => "Scandinavian";
    
    public void Relax()
    {
        Console.WriteLine("Relaxing on a cozy Scandinavian sofa");
    }
    
    public string GetDescription()
    {
        return "Scandinavian sofa: Light colors with clean lines";
    }
}

Step 3: Define Abstract Factory

The abstract factory interface is the cornerstone of the pattern. It declares methods for creating all product types, ensuring that any concrete factory can produce a complete set of compatible products. This interface acts as a contract that guarantees all products created by a factory will belong to the same family.

// Abstract Factory
public interface IFurnitureFactory
{
    IChair CreateChair();
    ITable CreateTable();
    ISofa CreateSofa();
}

This interface ensures that any concrete factory can create all three types of furniture.

Step 4: Implement Concrete Factories

Each concrete factory creates furniture from one style family:

// Modern Furniture Factory
public class ModernFurnitureFactory : IFurnitureFactory
{
    public IChair CreateChair() => new ModernChair();
    public ITable CreateTable() => new ModernTable();
    public ISofa CreateSofa() => new ModernSofa();
}

// Victorian Furniture Factory
public class VictorianFurnitureFactory : IFurnitureFactory
{
    public IChair CreateChair() => new VictorianChair();
    public ITable CreateTable() => new VictorianTable();
    public ISofa CreateSofa() => new VictorianSofa();
}

// Scandinavian Furniture Factory
public class ScandinavianFurnitureFactory : IFurnitureFactory
{
    public IChair CreateChair() => new ScandinavianChair();
    public ITable CreateTable() => new ScandinavianTable();
    public ISofa CreateSofa() => new ScandinavianSofa();
}

Each factory ensures all furniture comes from the same style family.

Step 5: Client Code

The client code demonstrates how to use the factories to create matching furniture sets. By depending on the abstract factory interface rather than concrete implementations, the client code remains flexible and can work with any furniture style without modification:

// Client: Furniture Shop
public class FurnitureShop
{
    private readonly IFurnitureFactory _factory;
    
    public FurnitureShop(IFurnitureFactory factory)
    {
        _factory = factory;
    }
    
    public void CreateFurnitureSet()
    {
        Console.WriteLine("Creating furniture set...\n");
        
        // All furniture guaranteed to be from the same style
        var chair = _factory.CreateChair();
        var table = _factory.CreateTable();
        var sofa = _factory.CreateSofa();
        
        Console.WriteLine($"Chair: {chair.GetDescription()}");
        Console.WriteLine($"Table: {table.GetDescription()}");
        Console.WriteLine($"Sofa: {sofa.GetDescription()}");
        
        Console.WriteLine($"\nAll furniture is {chair.Style} style - perfect match!");
    }
    
    public void DemonstrateUsage()
    {
        var chair = _factory.CreateChair();
        var table = _factory.CreateTable();
        var sofa = _factory.CreateSofa();
        
        chair.Sit();
        table.PlaceItems();
        sofa.Relax();
    }
}

// Usage Example
class Program
{
    static void Main(string[] args)
    {
        // Create Modern furniture set
        Console.WriteLine("=== Modern Furniture Set ===");
        var modernFactory = new ModernFurnitureFactory();
        var modernShop = new FurnitureShop(modernFactory);
        modernShop.CreateFurnitureSet();
        modernShop.DemonstrateUsage();
        
        Console.WriteLine("\n=== Victorian Furniture Set ===");
        var victorianFactory = new VictorianFurnitureFactory();
        var victorianShop = new FurnitureShop(victorianFactory);
        victorianShop.CreateFurnitureSet();
        victorianShop.DemonstrateUsage();
        
        Console.WriteLine("\n=== Scandinavian Furniture Set ===");
        var scandinavianFactory = new ScandinavianFurnitureFactory();
        var scandinavianShop = new FurnitureShop(scandinavianFactory);
        scandinavianShop.CreateFurnitureSet();
        scandinavianShop.DemonstrateUsage();
    }
}

Complete Working Example

Here's the complete, runnable code:

using System;

namespace AbstractFactoryExample
{
    // Abstract Products
    public interface IChair
    {
        string Style { get; }
        void Sit();
        string GetDescription();
    }
    
    public interface ITable
    {
        string Style { get; }
        void PlaceItems();
        string GetDescription();
    }
    
    public interface ISofa
    {
        string Style { get; }
        void Relax();
        string GetDescription();
    }
    
    // Modern Products
    public class ModernChair : IChair
    {
        public string Style => "Modern";
        public void Sit() => Console.WriteLine("Sitting on modern chair");
        public string GetDescription() => "Modern chair: Minimalist design";
    }
    
    public class ModernTable : ITable
    {
        public string Style => "Modern";
        public void PlaceItems() => Console.WriteLine("Placing on modern table");
        public string GetDescription() => "Modern table: Glass top";
    }
    
    public class ModernSofa : ISofa
    {
        public string Style => "Modern";
        public void Relax() => Console.WriteLine("Relaxing on modern sofa");
        public string GetDescription() => "Modern sofa: Low profile";
    }
    
    // Victorian Products
    public class VictorianChair : IChair
    {
        public string Style => "Victorian";
        public void Sit() => Console.WriteLine("Sitting on Victorian chair");
        public string GetDescription() => "Victorian chair: Ornate design";
    }
    
    public class VictorianTable : ITable
    {
        public string Style => "Victorian";
        public void PlaceItems() => Console.WriteLine("Placing on Victorian table");
        public string GetDescription() => "Victorian table: Mahogany wood";
    }
    
    public class VictorianSofa : ISofa
    {
        public string Style => "Victorian";
        public void Relax() => Console.WriteLine("Relaxing on Victorian sofa");
        public string GetDescription() => "Victorian sofa: Tufted back";
    }
    
    // Scandinavian Products
    public class ScandinavianChair : IChair
    {
        public string Style => "Scandinavian";
        public void Sit() => Console.WriteLine("Sitting on Scandinavian chair");
        public string GetDescription() => "Scandinavian chair: Light wood";
    }
    
    public class ScandinavianTable : ITable
    {
        public string Style => "Scandinavian";
        public void PlaceItems() => Console.WriteLine("Placing on Scandinavian table");
        public string GetDescription() => "Scandinavian table: Birch wood";
    }
    
    public class ScandinavianSofa : ISofa
    {
        public string Style => "Scandinavian";
        public void Relax() => Console.WriteLine("Relaxing on Scandinavian sofa");
        public string GetDescription() => "Scandinavian sofa: Clean lines";
    }
    
    // Abstract Factory
    public interface IFurnitureFactory
    {
        IChair CreateChair();
        ITable CreateTable();
        ISofa CreateSofa();
    }
    
    // Concrete Factories
    public class ModernFurnitureFactory : IFurnitureFactory
    {
        public IChair CreateChair() => new ModernChair();
        public ITable CreateTable() => new ModernTable();
        public ISofa CreateSofa() => new ModernSofa();
    }
    
    public class VictorianFurnitureFactory : IFurnitureFactory
    {
        public IChair CreateChair() => new VictorianChair();
        public ITable CreateTable() => new VictorianTable();
        public ISofa CreateSofa() => new VictorianSofa();
    }
    
    public class ScandinavianFurnitureFactory : IFurnitureFactory
    {
        public IChair CreateChair() => new ScandinavianChair();
        public ITable CreateTable() => new ScandinavianTable();
        public ISofa CreateSofa() => new ScandinavianSofa();
    }
    
    // Client
    public class FurnitureShop
    {
        private readonly IFurnitureFactory _factory;
        
        public FurnitureShop(IFurnitureFactory factory)
        {
            _factory = factory;
        }
        
        public void DisplayFurnitureSet()
        {
            var chair = _factory.CreateChair();
            var table = _factory.CreateTable();
            var sofa = _factory.CreateSofa();
            
            Console.WriteLine($"\n{chair.Style} Furniture Set:");
            Console.WriteLine($"  - {chair.GetDescription()}");
            Console.WriteLine($"  - {table.GetDescription()}");
            Console.WriteLine($"  - {sofa.GetDescription()}");
        }
    }
    
    // Program
    class Program
    {
        static void Main(string[] args)
        {
            // Modern set
            var modernFactory = new ModernFurnitureFactory();
            var modernShop = new FurnitureShop(modernFactory);
            modernShop.DisplayFurnitureSet();
            
            // Victorian set
            var victorianFactory = new VictorianFurnitureFactory();
            var victorianShop = new FurnitureShop(victorianFactory);
            victorianShop.DisplayFurnitureSet();
            
            // Scandinavian set
            var scandinavianFactory = new ScandinavianFurnitureFactory();
            var scandinavianShop = new FurnitureShop(scandinavianFactory);
            scandinavianShop.DisplayFurnitureSet();
        }
    }
}

Extending the Example: Adding New Styles

One of Abstract Factory's greatest strengths is its easy extensibility. The pattern follows the Open/Closed Principle—open for extension, closed for modification. To add a new style (e.g., Industrial), we just add new classes without modifying existing code:

// New products
public class IndustrialChair : IChair { /* ... */ }
public class IndustrialTable : ITable { /* ... */ }
public class IndustrialSofa : ISofa { /* ... */ }

// New factory
public class IndustrialFurnitureFactory : IFurnitureFactory
{
    public IChair CreateChair() => new IndustrialChair();
    public ITable CreateTable() => new IndustrialTable();
    public ISofa CreateSofa() => new IndustrialSofa();
}

No changes needed to existing code! This demonstrates the Open/Closed Principle.

Adding Configuration Support

We can make the factory selection configurable:

public class FurnitureFactorySelector
{
    public static IFurnitureFactory GetFactory(string style)
    {
        return style.ToLower() switch
        {
            "modern" => new ModernFurnitureFactory(),
            "victorian" => new VictorianFurnitureFactory(),
            "scandinavian" => new ScandinavianFurnitureFactory(),
            _ => throw new ArgumentException($"Unknown style: {style}")
        };
    }
}

// Usage
var factory = FurnitureFactorySelector.GetFactory("modern");
var shop = new FurnitureShop(factory);
shop.DisplayFurnitureSet();

Integration with Dependency Injection

Abstract Factory integrates seamlessly with dependency injection containers, which is one of the most common ways to use this pattern in modern .NET applications. DI containers can manage factory lifetimes and provide factories to dependent services automatically:

// In Startup.cs or Program.cs
services.AddScoped<IFurnitureFactory>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    var style = config["FurnitureStyle"];
    return FurnitureFactorySelector.GetFactory(style);
});

// In controller or service
public class FurnitureController
{
    private readonly IFurnitureFactory _factory;
    
    public FurnitureController(IFurnitureFactory factory)
    {
        _factory = factory;
    }
    
    public IActionResult GetFurnitureSet()
    {
        var chair = _factory.CreateChair();
        var table = _factory.CreateTable();
        var sofa = _factory.CreateSofa();
        // ...
    }
}

Testing the Implementation

Testing Abstract Factory implementations is straightforward because the pattern promotes testability through interfaces and dependency injection. You can easily create test doubles (mocks or stubs) of factories and verify that products are created correctly and are compatible with each other:

[Fact]
public void ModernFactory_CreatesModernFurniture()
{
    // Arrange
    var factory = new ModernFurnitureFactory();
    
    // Act
    var chair = factory.CreateChair();
    var table = factory.CreateTable();
    var sofa = factory.CreateSofa();
    
    // Assert
    Assert.Equal("Modern", chair.Style);
    Assert.Equal("Modern", table.Style);
    Assert.Equal("Modern", sofa.Style);
    Assert.IsType<ModernChair>(chair);
    Assert.IsType<ModernTable>(table);
    Assert.IsType<ModernSofa>(sofa);
}

[Fact]
public void FurnitureShop_UsesFactoryCorrectly()
{
    // Arrange
    var factory = new ModernFurnitureFactory();
    var shop = new FurnitureShop(factory);
    
    // Act & Assert - should not throw
    shop.DisplayFurnitureSet();
}

[Fact]
public void AllFurnitureFromFactory_MatchesStyle()
{
    // Arrange
    var factory = new VictorianFurnitureFactory();
    
    // Act
    var chair = factory.CreateChair();
    var table = factory.CreateTable();
    var sofa = factory.CreateSofa();
    
    // Assert - all must match
    Assert.Equal(chair.Style, table.Style);
    Assert.Equal(table.Style, sofa.Style);
}

Real-World Variations

The Abstract Factory pattern is flexible and can be adapted to various scenarios. Here are some common variations you might encounter:

Variation 1: Adding More Product Types

Extending the pattern to include additional product types is straightforward. You can easily add more furniture types by extending the factory interface and implementing the new products for each family:

public interface IFurnitureFactory
{
    IChair CreateChair();
    ITable CreateTable();
    ISofa CreateSofa();
    ILamp CreateLamp(); // New product type
    ICabinet CreateCabinet(); // New product type
}

Each concrete factory implements the new methods, ensuring all products match.

Variation 2: Factory Registry Pattern

For dynamic factory selection:

public class FurnitureFactoryRegistry
{
    private readonly Dictionary<string, IFurnitureFactory> _factories;
    
    public FurnitureFactoryRegistry()
    {
        _factories = new Dictionary<string, IFurnitureFactory>
        {
            { "modern", new ModernFurnitureFactory() },
            { "victorian", new VictorianFurnitureFactory() },
            { "scandinavian", new ScandinavianFurnitureFactory() }
        };
    }
    
    public IFurnitureFactory GetFactory(string style)
    {
        return _factories.TryGetValue(style.ToLower(), out var factory)
            ? factory
            : throw new ArgumentException($"Style not found: {style}");
    }
    
    public void RegisterFactory(string style, IFurnitureFactory factory)
    {
        _factories[style.ToLower()] = factory;
    }
}

Key Takeaways

This real-world furniture shop example demonstrates several important aspects of the Abstract Factory pattern:

  1. Family Guarantee: All furniture from a factory matches the same style
  2. Easy Extension: Adding new styles doesn't require changing existing code
  3. Type Safety: Compiler ensures factories create compatible products
  4. Testability: Easy to test with mock factories
  5. Clean Code: No scattered conditional logic

Conclusion

The furniture shop example perfectly illustrates Abstract Factory's strengths. We have:

  • Multiple product types (chair, table, sofa)
  • Multiple families (Modern, Victorian, Scandinavian)
  • Guaranteed compatibility within each family
  • Easy extensibility for new styles

This pattern is ideal whenever you need to ensure objects work together as a cohesive group. The furniture example makes the concept concrete and easy to understand.

Try modifying the code: add new furniture types, create new styles, or integrate it with a configuration system. Hands-on experimentation is the best way to internalize the pattern.

For more design pattern content, explore The Big List of Design Patterns.

Frequently Asked Questions

Can I use this example as a starting point for my project?

Absolutely! This is a complete, working example you can adapt. Change the product types and families to match your domain (UI components, payment systems, database providers, etc.).

How do I add new furniture types to existing factories?

Add the new product interface, implement it for each style, add the creation method to the factory interface, and implement it in all concrete factories. The pattern makes this straightforward.

What if I need furniture from different styles?

That's exactly what Abstract Factory prevents! If you truly need mixed styles, you might need a different design. But usually, the requirement is for matching sets, which Abstract Factory handles perfectly.

Can factories have different numbers of products?

Technically yes, but it's not recommended. Abstract Factory works best when all factories create the same set of product types. If product counts vary, consider if Abstract Factory is still the right pattern.

How do I handle factory selection at runtime?

Use a factory selector or registry pattern (shown in the article). You can also use dependency injection to configure the factory based on configuration, user preferences, or environment settings.

Is this example too simple for real applications?

The example demonstrates the core concept clearly. Real applications might have more complex products, additional factory logic, or integration with frameworks, but the pattern structure remains the same.

Can I combine Abstract Factory with other patterns?

Yes! Common combinations include: Singleton for factory instances, Strategy for factory selection, Builder for complex product construction, and Dependency Injection for factory management.

Abstract Factory Design Pattern in C#: Complete Guide with Examples

Master the Abstract Factory design pattern in C# with complete code examples, real-world scenarios, and practical implementation guidance for creating families of related objects.

Abstract Factory vs Factory Method Pattern in C#: Key Differences Explained

Understand the differences between Abstract Factory and Factory Method patterns in C# with code examples, use cases, and guidance on when to use each pattern.

When to Use Abstract Factory Pattern in C#: Decision Guide with Examples

Learn when to use Abstract Factory pattern in C# with clear decision criteria, code examples, and scenarios. Understand the signs that indicate Abstract Factory is the right choice.

An error has occurred. This application may no longer respond until reloaded. Reload x