Skip to main content

Abstract Factory Pattern

Definition​

The Abstract Factory provides an interface for creating families of related or dependent objects without specifying their concrete classes. Each concrete factory produces a complete product family that works together.

Coffee Shop Example​

A coffee shop chain operates in three regions β€” Italy, Vietnam, and America. Each region has its own style of coffee, pastry, and packaging. The Abstract Factory ensures all items from one region are consistent.

Structure​

Product Interfaces​

// Product family β€” Coffee
public abstract class Coffee
{
public string Name { get; init; } = "";
public double Price { get; init; }
public List<string> Ingredients { get; init; } = new();
public override string ToString() => $"{Name} β€” ${Price:F2} [{string.Join(", ", Ingredients)}]";
}

// Product family β€” Pastry
public abstract class Pastry
{
public string Name { get; init; } = "";
public double Price { get; init; }
public override string ToString() => $"{Name} β€” ${Price:F2}";
}

// Product family β€” Packaging
public abstract class Packaging
{
public string Material { get; init; } = "";
public string Style { get; init; } = "";
public override string ToString() => $"{Style} ({Material})";
}

Concrete Products​

// --- Italian Products ---
public class ItalianEspresso : Coffee
{
public ItalianEspresso()
{
Name = "Italian Espresso";
Price = 1.50;
Ingredients = new() { "Fine-ground Arabica", "Pressed in portafilter" };
}
}

public class Cornetto : Pastry
{
public Cornetto()
{
Name = "Cornetto";
Price = 2.00;
}
}

public class CeramicCup : Packaging
{
public CeramicCup()
{
Material = "Ceramic";
Style = "Traditional demitasse";
}
}

// --- Vietnamese Products ---
public class CaPheSuaDa : Coffee
{
public CaPheSuaDa()
{
Name = "CΓ  PhΓͺ Sα»―a ĐÑ";
Price = 1.00;
Ingredients = new() { "Robusta beans", "Condensed milk", "Ice" };
}
}

public class BanhMi : Pastry
{
public BanhMi()
{
Name = "BÑnh Mì";
Price = 1.50;
}
}

public class GlassWithMetalFilter : Packaging
{
public GlassWithMetalFilter()
{
Material = "Glass + Phin filter";
Style = "Traditional Vietnamese";
}
}

// --- American Products ---
public class DripCoffee : Coffee
{
public DripCoffee()
{
Name = "Drip Coffee";
Price = 3.00;
Ingredients = new() { "Medium roast", "Paper filter brew" };
}
}

public class Muffin : Pastry
{
public Muffin()
{
Name = "Blueberry Muffin";
Price = 3.50;
}
}

public class PaperCup : Packaging
{
public PaperCup()
{
Material = "Paper";
Style = "Disposable to-go cup";
}
}

Abstract Factory & Concrete Factories​

// Abstract Factory
public interface ICoffeeShopFactory
{
Coffee CreateCoffee();
Pastry CreatePastry();
Packaging CreatePackaging();
string Region { get; }
}

// Concrete Factories
public class ItalianFactory : ICoffeeShopFactory
{
public string Region => "Italy";
public Coffee CreateCoffee() => new ItalianEspresso();
public Pastry CreatePastry() => new Cornetto();
public Packaging CreatePackaging() => new CeramicCup();
}

public class VietnameseFactory : ICoffeeShopFactory
{
public string Region => "Vietnam";
public Coffee CreateCoffee() => new CaPheSuaDa();
public Pastry CreatePastry() => new BanhMi();
public Packaging CreatePackaging() => new GlassWithMetalFilter();
}

public class AmericanFactory : ICoffeeShopFactory
{
public string Region => "America";
public Coffee CreateCoffee() => new DripCoffee();
public Pastry CreatePastry() => new Muffin();
public Packaging CreatePackaging() => new PaperCup();
}

Client β€” The Coffee Shop​

public class CoffeeShop
{
private readonly ICoffeeShopFactory _factory;

public CoffeeShop(ICoffeeShopFactory factory)
{
_factory = factory;
}

public void ServeCombo()
{
var coffee = _factory.CreateCoffee();
var pastry = _factory.CreatePastry();
var packaging = _factory.CreatePackaging();

Console.WriteLine($"=== {_factory.Region} Combo ===");
Console.WriteLine($" Coffee: {coffee}");
Console.WriteLine($" Pastry: {pastry}");
Console.WriteLine($" Packaging: {packaging}");
}
}

// Usage β€” switch regions by swapping the factory
var shops = new CoffeeShop[]
{
new(new ItalianFactory()),
new(new VietnameseFactory()),
new(new AmericanFactory()),
};

foreach (var shop in shops)
shop.ServeCombo();

// Output:
// === Italy Combo ===
// Coffee: Italian Espresso β€” $1.50 [Fine-ground Arabica, Pressed in portafilter]
// Pastry: Cornetto β€” $2.00
// Packaging: Traditional demitasse (Ceramic)
// === Vietnam Combo ===
// Coffee: CΓ  PhΓͺ Sα»―a ĐÑ β€” $1.00 [Robusta beans, Condensed milk, Ice]
// Pastry: BÑnh Mì — $1.50
// Packaging: Traditional Vietnamese (Glass + Phin filter)
// === America Combo ===
// Coffee: Drip Coffee β€” $3.00 [Medium roast, Paper filter brew]
// Pastry: Blueberry Muffin β€” $3.50
// Packaging: Disposable to-go cup (Paper)

Factory Method vs Abstract Factory​

Factory MethodAbstract Factory
CreatesOne productFamily of related products
MechanismInheritance (override a method)Composition (inject a factory)
Focus"Which subclass creates this?""Which family of products?"
ExampleCreateCoffee() overridden per channelICoffeeShopFactory with region-specific factories

.NET Real-World Usage​

  • DbProviderFactory β€” creates connections, commands, parameters for a specific DB (SQL Server, PostgreSQL, etc.)
  • UI frameworks β€” theme factories that create buttons, text boxes, scrollbars for a specific look-and-feel
  • Cross-platform abstractions β€” factories that create platform-specific implementations

When to Use​

  • The system needs to be independent of how products are created
  • Products are designed to work together as a family
  • You need to switch between product families at runtime (themes, regions, platforms)
  • You want to enforce consistency β€” products from the same family should always be used together

When NOT to Use​

  • Only one product type β€” use Factory Method instead
  • Products don't form a family β€” don't force unrelated objects into a factory
  • The product family never changes β€” a simple factory with configuration is lighter

Key Takeaways​

  • Abstract Factory creates families of related products through a common interface
  • Switch the entire family by swapping the factory β€” no client code changes
  • Each concrete factory guarantees consistency: all products belong to the same "theme"
  • Often implemented using multiple Factory Methods inside one factory class

Interview Questions​

Q: How is Abstract Factory different from Factory Method? Factory Method creates one product via inheritance. Abstract Factory creates a family of products via composition. Abstract Factory often uses Factory Methods internally.

Q: How do you add a new product to the family? You add a new method to ICoffeeShopFactory and implement it in every concrete factory. This is the pattern's weakness β€” it breaks OCP for product changes.

Q: How do you add a new product family? Create a new concrete factory (e.g., JapaneseFactory). This is OCP-compliant β€” existing code doesn't change.

  • Factory Method β€” single product creation
  • Builder β€” step-by-step construction of complex objects
  • SOLID β€” DIP β€” depend on abstractions (ICoffeeShopFactory), not concrete factories