Exploring Abstract Factory Design Pattern in iOS

Kevin TopollajKevin Topollaj
5 min read

๐Ÿ“ 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! ๐Ÿ™

18
Subscribe to my newsletter

Read articles from Kevin Topollaj directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Kevin Topollaj
Kevin Topollaj

Greetings! I am Kevin, an accomplished iOS Software Developer with expertise in software development, architecture, testing, maintenance, and debugging of complex software systems. I have more than five years of experience in developing high-quality iOS apps using Swift programming language, UIKit, SwiftUI and Combine frameworks. With a keen eye for detail and a strong understanding of the latest cutting-edge development tools and procedures, I am dedicated to delivering exceptional results that meet or exceed the needs of my clients. I have a proven track record of working both independently and collaboratively as part of a productive team, and I am able to effectively self-manage during independent projects. Thank you for visiting my personal blog, where I look forward to sharing my knowledge and expertise in iOS development and contributing to the vibrant developer community.