Software design patterns play a crucial role in developing well-structured and maintainable code. Among the most commonly used design patterns are the Builder, Factory, and Abstract Factory patterns.
While they all belong to the creational design pattern category, each pattern serves a different purpose and exhibits distinct characteristics.
The builder pattern is employed when the construction of complex objects requires multiple steps, allowing for flexible creation and customization. The factory pattern, on the other hand, focuses on creating objects of a specific type without exposing the instantiation logic to the client. Finally, the abstract factory pattern provides an interface for creating families of related or dependent objects, ensuring their compatibility and interchangeability.
In this article, we will explore the differences between Builder, Factory, and Abstract Factory, highlighting their individual use cases, benefits, and implementation approaches.
What is a builder Pattern?
The Builder pattern is used to construct complex objects step by step while maintaining control over the object’s construction process. It is particularly useful when dealing with objects that have multiple optional parameters or configurations.
Core Components
- Director: Controls the construction process using a builder object.
- Builder: Abstract interface or class that defines the steps to create the object.
- Concrete Builder: Implements the Builder interface and provides specific implementations for constructing the object.
- Product: Represents the complex object being built.
Real-World Example
Consider a scenario where you are building a car. The Builder pattern allows you to define a CarBuilder with methods to set various optional features such as color, engine type, and navigation system.
The Director, in this case, would be responsible for executing the builder’s methods in a specific order to construct the car. The final product is a fully customized car with only the selected features.
using System; // Product class class Car { public string Color { get; set; } public string Engine { get; set; } public bool HasNavigation { get; set; } public void Display() { Console.WriteLine($"Car Details: Color - {Color}, Engine - {Engine}, Navigation - {(HasNavigation ? "Yes" : "No")}"); } } // Builder interface interface ICarBuilder { void SetColor(string color); void SetEngine(string engine); void SetNavigation(bool hasNavigation); Car GetCar(); } // Concrete builder class CarBuilder : ICarBuilder { private Car car; public CarBuilder() { car = new Car(); } public void SetColor(string color) { car.Color = color; } public void SetEngine(string engine) { car.Engine = engine; } public void SetNavigation(bool hasNavigation) { car.HasNavigation = hasNavigation; } public Car GetCar() { return car; } } // Director class class CarDirector { public Car ConstructSportsCar(ICarBuilder builder) { builder.SetColor("Red"); builder.SetEngine("V8"); builder.SetNavigation(false); return builder.GetCar(); } public Car ConstructSUV(ICarBuilder builder) { builder.SetColor("Blue"); builder.SetEngine("V6"); builder.SetNavigation(true); return builder.GetCar(); } } class Program { static void Main() { CarDirector director = new CarDirector(); CarBuilder builder = new CarBuilder(); Car sportsCar = director.ConstructSportsCar(builder); sportsCar.Display(); Car suv = director.ConstructSUV(builder); suv.Display(); Console.ReadLine(); } }
Benefits
- Provides a flexible and fluent API for constructing complex objects.
- Allows step-by-step construction, enabling control over object creation.
- Supports the creation of different variations of objects using the same building process.
What is Factory Pattern?
The Factory pattern focuses on creating objects without exposing the object instantiation logic to the client. It provides a centralized method or class responsible for creating and returning instances of related classes based on a given input or condition
Core Components
- Creator: Abstract interface or class that declares the factory method(s).
- Concrete Creator: Implements the factory method(s) and produces the specific instances.
- Product: Represents the objects created by the factory.
Real-World Example
Suppose you have a software application that generates various documents, such as reports, invoices, and letters. The Factory pattern allows you to define a DocumentFactory that takes a document type as input and returns the corresponding document object.
The client code only needs to interact with the factory, abstracting away the specific document creation logic.
using System; // Product interface interface IDocument { void Open(); void Close(); } // Concrete products class Report : IDocument { public void Open() { Console.WriteLine("Opening Report"); } public void Close() { Console.WriteLine("Closing Report"); } } class Invoice : IDocument { public void Open() { Console.WriteLine("Opening Invoice"); } public void Close() { Console.WriteLine("Closing Invoice"); } } class Letter : IDocument { public void Open() { Console.WriteLine("Opening Letter"); } public void Close() { Console.WriteLine("Closing Letter"); } } // Factory class class DocumentFactory { public IDocument CreateDocument(string documentType) { IDocument document; switch (documentType.ToLower()) { case "report": document = new Report(); break; case "invoice": document = new Invoice(); break; case "letter": document = new Letter(); break; default: throw new ArgumentException("Invalid document type."); } return document; } } class Program { static void Main() { DocumentFactory factory = new DocumentFactory(); IDocument report = factory.CreateDocument("report"); report.Open(); report.Close(); IDocument invoice = factory.CreateDocument("invoice"); invoice.Open(); invoice.Close(); IDocument letter = factory.CreateDocument("letter"); letter.Open(); letter.Close(); Console.ReadLine(); } }
Benifits
- Encapsulates object creation logic, promoting loose coupling between the client and the created objects.
- Simplifies object creation by providing a centralized and consistent way to instantiate objects.
- Enables easy extension by adding new concrete classes to the factory without modifying client code.
What is Abstract Factory Pattern?
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It allows the client to create objects that belong to the same family and ensures that all the created objects are compatible.
Core Components
- Abstract Factory: Declares the creation methods for different types of related objects.
- Concrete Factory: Implements the creation methods declared in the Abstract Factory, producing concrete objects of a specific family.
- Abstract Product: Declares the interface for a group of related products.
- Concrete Product: Implements the Abstract Product interface, representing a specific product of the family.
Real-World Example
Consider a graphical user interface (GUI) framework that supports multiple operating systems. The Abstract Factory pattern allows you to define an abstract GUIFactory with methods to create UI components such as buttons and checkboxes.
Concrete implementations of GUIFactory, like WindowsGUIFactory and MacGUIFactory, provide the actual creation logic for these components based on the specific operating system.
This ensures that all UI components within a particular family are consistent.
using System; // Abstract Product A interface IButton { void Render(); } // Concrete Product A1 class WindowsButton : IButton { public void Render() { Console.WriteLine("Rendering a Windows button."); } } // Concrete Product A2 class MacButton : IButton { public void Render() { Console.WriteLine("Rendering a Mac button."); } } // Abstract Product B interface ICheckbox { void Render(); } // Concrete Product B1 class WindowsCheckbox : ICheckbox { public void Render() { Console.WriteLine("Rendering a Windows checkbox."); } } // Concrete Product B2 class MacCheckbox : ICheckbox { public void Render() { Console.WriteLine("Rendering a Mac checkbox."); } } // Abstract Factory interface IGUIFactory { IButton CreateButton(); ICheckbox CreateCheckbox(); } // Concrete Factory for Windows class WindowsGUIFactory : IGUIFactory { public IButton CreateButton() { return new WindowsButton(); } public ICheckbox CreateCheckbox() { return new WindowsCheckbox(); } } // Concrete Factory for Mac class MacGUIFactory : IGUIFactory { public IButton CreateButton() { return new MacButton(); } public ICheckbox CreateCheckbox() { return new MacCheckbox(); } } // Client code class Client { private IButton _button; private ICheckbox _checkbox; public Client(IGUIFactory factory) { _button = factory.CreateButton(); _checkbox = factory.CreateCheckbox(); } public void RenderUI() { _button.Render(); _checkbox.Render(); } } // Usage example class Program { static void Main() { // Create a Windows GUI IGUIFactory windowsFactory = new WindowsGUIFactory(); Client windowsClient = new Client(windowsFactory); windowsClient.RenderUI(); Console.WriteLine(); // Create a Mac GUI IGUIFactory macFactory = new MacGUIFactory(); Client macClient = new Client(macFactory); macClient.RenderUI(); } }
Conclusion
In conclusion, the Builder, Factory, and Abstract Factory patterns are all creational design patterns that serve different purposes in software development.