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 Method | Abstract Factory | |
|---|---|---|
| Creates | One product | Family of related products |
| Mechanism | Inheritance (override a method) | Composition (inject a factory) |
| Focus | "Which subclass creates this?" | "Which family of products?" |
| Example | CreateCoffee() overridden per channel | ICoffeeShopFactory 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.
Related Topicsβ
- Factory Method β single product creation
- Builder β step-by-step construction of complex objects
- SOLID β DIP β depend on abstractions (
ICoffeeShopFactory), not concrete factories