The Abstract Factory is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern ensures that objects created by a factory belong to a consistent family, promoting flexibility and scalability in the codebase.
When to Use the Abstract Factory Pattern?
- When your system needs to work with multiple families of related objects.
- When ensuring that related objects are used together is a priority.
- When you want to abstract the instantiation process from the client code.
Key Features of the Abstract Factory Pattern
- Family of Products: Supports the creation of families of related or dependent objects.
- Abstraction: Separates the client code from the actual creation process.
- Consistency: Ensures that products from the same family work well together.
- Encapsulation: Hides the details of the object creation from the client code.
What are the Pros and Cons?
Pros
- Flexibility: Adding new families of products requires minimal changes to the client code.
- Decoupling: Promotes loose coupling by separating the client code from the concrete implementations.
- Consistency: Ensures that only compatible products are used together.
Cons
- Complexity: Can introduce multiple classes and interfaces, increasing complexity.
- Extensibility Challenges: Adding new product types to existing families may require changes to all factory interfaces.
Example: Theme Park
Consider a theme park that offers experiences in two styles: Futuristic and Medieval. Each style includes a consistent set of items such as costumes, rides, and menus. The Abstract Factory pattern acts as the system ensuring that all items in a visitor’s experience belong to the same theme. For example, selecting the Futuristic theme provides a spacesuit costume, a spaceship ride, and a tech-inspired menu, while the Medieval theme offers knight’s armor, a horse ride, and a rustic feast.
Class Diagram
Implementation in C#
Let’s see how we can use the Abstract Factory design pattern in practice. Consider the theme park scenario
///
/// Abstract Product - Ride
///
public interface ICostume
{
void Wear();
}
///
/// Abstract Product - Costume
///
public interface IRide
{
void Experience();
}
///
/// Concrete Product - Futuristic Costume
///
public class FuturisticCostume : ICostume
{
public void Wear()
{
Console.WriteLine("Wearing a spacesuit.");
}
}
///
/// Concrete Product - Medieval Costume
///
public class MedievalCostume : ICostume
{
public void Wear()
{
Console.WriteLine("Wearing a knight's armor.");
}
}
///
/// Concrete Product - Futuristic Ride
///
public class FuturisticRide : IRide
{
public void Experience()
{
Console.WriteLine("Riding a spaceship.");
}
}
///
/// Concrete Product - Medieval Ride
///
public class MedievalRide : IRide
{
public void Experience()
{
Console.WriteLine("Riding a horse.");
}
}
///
/// Abstract Factory
///
public interface IThemeParkFactory
{
ICostume CreateCostume();
IRide CreateRide();
}
///
/// Concrete Factory - Futuristic Theme
///
public class FuturisticThemeParkFactory : IThemeParkFactory
{
public ICostume CreateCostume()
{
return new FuturisticCostume();
}
public IRide CreateRide()
{
return new FuturisticRide();
}
}
///
/// Concrete Factory - Medieval Theme
///
public class MedievalThemeParkFactory : IThemeParkFactory
{
public ICostume CreateCostume()
{
return new MedievalCostume();
}
public IRide CreateRide()
{
return new MedievalRide();
}
}
///
/// Client Code
///
public class ThemePark
{
private readonly ICostume _costume;
private readonly IRide _ride;
public ThemePark(IThemeParkFactory factory)
{
_costume = factory.CreateCostume();
_ride = factory.CreateRide();
}
public void EnjoyExperience()
{
_costume.Wear();
_ride.Experience();
}
}
// Main Program
class Program
{
static void Main(string[] args)
{
// Futuristic Theme
IThemeParkFactory futuristicFactory = new FuturisticThemeParkFactory();
ThemePark futuristicPark = new ThemePark(futuristicFactory);
Console.WriteLine("Futuristic Theme Park Experience:");
futuristicPark.EnjoyExperience();
Console.WriteLine();
// Medieval Theme
IThemeParkFactory medievalFactory = new MedievalThemeParkFactory();
ThemePark medievalPark = new ThemePark(medievalFactory);
Console.WriteLine("Medieval Theme Park Experience:");
medievalPark.EnjoyExperience();
}
}
Here is the output from the application
Futuristic Theme Park Experience:
Wearing a spacesuit.
Riding a spaceship.
Medieval Theme Park Experience:
Wearing a knight's armor.
Riding a horse.
Explanation of the Code
- Abstract Products:
ICostume
andIRide
define the interfaces for costumes and rides. - Concrete Products:
FuturisticCostume
,MedievalCostume
,FuturisticRide
, andMedievalRide
implement the respective interfaces. - Abstract Factory:
IThemeParkFactory
defines methods to create costumes and rides. - Concrete Factories:
FuturisticThemeParkFactory
andMedievalThemeParkFactory
implement the abstract factory to create themed products. - Client Code: The
ThemePark
class uses the factory to create and use products without relying on their concrete implementations.
Summary
The Abstract Factory design pattern is an excellent choice when your system requires families of related objects to be used together consistently. While it introduces some complexity, the benefits of decoupling, consistency, and scalability make it an essential pattern for building flexible and maintainable software systems.