๐ Introduction
The Abstract Factory
design pattern is a creational pattern that provides a protocol for creating families of related or dependent objects without specifying their concrete classes.
It allows clients to create objects without knowing the specific classes they belong to, promoting loose coupling and enhancing flexibility in the codebase.
The main idea behind the Abstract Factory
pattern is to define a Abstract Factory
protocol as a blueprint that will be used for creating different types of related objects.
Concrete Factory
classes are then responsible for implementing these methods and producing concrete instances of the objects.
This approach allows clients to work with the abstract factory and its product protocols, without being tightly coupled to specific implementations.
๐ท Key Components
1. Abstract Factory
: Defines the protocol for creating the families of related objects.
2. Concrete Factories
: Implement the Abstract Factory
protocol and are responsible for creating specific families of objects.
3. Abstract Products
: Define the protocols for the different types of objects created by the factory.
4. Concrete Products
: Implement the Abstract Product
protocol and represent the specific objects created by the Concrete Factories
.
๐จ Diagram
๐จ๐ผโ๐ป Implementation
A great example of utilizing the Abstract Factory
design pattern can be a food delivery app, there are different types of restaurants offering various cuisines.
Each restaurant can have its own menu items, such as pizzas, burgers, or sushi.
The Abstract Factory
pattern can be used to create families of related objects, where each family corresponds to a specific restaurant and its menu items.
Let's start by defining the Abstract Factory
and Abstract Products
protocols:
// Abstract factory
protocol RestaurantFactory {
func createAppetizer() -> Appetizer
func createMainCourse() -> MainCourse
func createDessert() -> Dessert
}
// Abstract products
protocol Appetizer {
func display()
}
protocol MainCourse {
func display()
}
protocol Dessert {
func display()
}
Next, we create Concrete Factories
that implement the RestaurantFactory
protocol for different types of restaurants:
// Concrete factory for an Italian restaurant
class ItalianRestaurantFactory: RestaurantFactory {
func createAppetizer() -> Appetizer {
return ItalianAppetizer()
}
func createMainCourse() -> MainCourse {
return ItalianMainCourse()
}
func createDessert() -> Dessert {
return ItalianDessert()
}
}
// Concrete factory for a Mexican restaurant
class MexicanRestaurantFactory: RestaurantFactory {
func createAppetizer() -> Appetizer {
return MexicanAppetizer()
}
func createMainCourse() -> MainCourse {
return MexicanMainCourse()
}
func createDessert() -> Dessert {
return MexicanDessert()
}
}
Now, we define Concrete Products
implementations for each type of restaurant and their menu items:
// Italian restaurant products
class ItalianAppetizer: Appetizer {
func display() {
print("Italian appetizer: Bruschetta")
}
}
class ItalianMainCourse: MainCourse {
func display() {
print("Italian main course: Margherita pizza")
}
}
class ItalianDessert: Dessert {
func display() {
print("Italian dessert: Tiramisu")
}
}
// Mexican restaurant products
class MexicanAppetizer: Appetizer {
func display() {
print("Mexican appetizer: Guacamole")
}
}
class MexicanMainCourse: MainCourse {
func display() {
print("Mexican main course: Tacos")
}
}
class MexicanDessert: Dessert {
func display() {
print("Mexican dessert: Churros")
}
}
In the client code, we can utilize the abstract factory and its products to create and display menu items without knowing the specific restaurant or its menu items:
class FoodDelivery {
private let restaurantFactory: RestaurantFactory
init(restaurantFactory: RestaurantFactory) {
self.restaurantFactory = restaurantFactory
}
func displayMenu() {
let appetizer = restaurantFactory.createAppetizer()
let mainCourse = restaurantFactory.createMainCourse()
let dessert = restaurantFactory.createDessert()
print("Appetizer:")
appetizer.display()
print("Main Course:")
mainCourse.display()
print("Dessert:")
dessert.display()
}
}
Now, when we run the code and use a specific restaurant, we can create and display the menu items using the abstract factory:
// Create instances of concrete factories
let italianRestaurantFactory = ItalianRestaurantFactory()
let mexicanRestaurantFactory = MexicanRestaurantFactory()
// Create instances of FoodDelivery using the desired factories
let italianFoodDelivery = FoodDelivery(restaurantFactory: italianRestaurantFactory)
let mexicanFoodDelivery = FoodDelivery(restaurantFactory: mexicanRestaurantFactory)
// Display menus using the respective instances
italianFoodDelivery.displayMenu()
mexicanFoodDelivery.displayMenu()
Output:
/*
Appetizer:
Italian appetizer: Bruschetta
Main Course:
Italian main course: Margherita pizza
Dessert:
Italian dessert: Tiramisu
Appetizer:
Mexican appetizer: Guacamole
Main Course:
Mexican main course: Tacos
Dessert:
Mexican dessert: Churros
*/
โ Positive aspects
1. Encourages modularity and extensibility
: The Abstract Factory
pattern provides a structured approach to creating families of related objects. It allows for the easy addition of new concrete factories and products without modifying the existing codebase.
2. Promotes loose coupling
: The client code depends only on the Abstract Factory
and Abstract Product
protocols, ensuring low coupling between the client and the concrete implementations. This facilitates easier maintenance and testing.
3. Supports consistent object creation
: The Abstract Factory
pattern ensures that the created objects within a family are consistent and compatible with each other.
โ Negative aspects
1. Increased complexity
: As the number of families of related objects and concrete factories grows, the complexity of the codebase can increase.
2. Limited flexibility
: The Abstract Factory
pattern is best suited when there are well-defined families of objects with limited variation. If the object creation logic varies significantly or they are created dynamically, other design patterns may be more suitable.
๐ Conclusions
The Abstract Factory
pattern provides a structured approach for creating families of related objects in a modular and extensible manner.
It promotes loose coupling and consistent object creation, making it a valuable pattern in scenarios where different families of objects need to be created based on a specific context or configuration.
However, it's important to carefully consider the complexity and variation in object creation to determine whether the Abstract Factory
pattern is the most appropriate choice for a given situation in your application.
If you want to be notified of the upcoming articles you can subscribe to the Newsletter and support The iOS Mentor blog using Buy Me a Coffee.
You also have the option to support this blog as a sponsor, contributing to the growth of our iOS Development community.
I am also available on LinkedIn and GitHub so let's connect!
Thanks for reading everyone and enjoy the rest of your day! ๐