Exploring Gang of Four Patterns in iOS Development

Uplift iOS Interview

The Guide is for YOU if
  • You are preparing for an iOS interview and want to improve your skills and knowledge and looking to level up your interview game and land your dream job.
  • You want to gain confidence and ease during iOS interviews by learning expert tips and curated strategies.
  • You want access to a comprehensive list of iOS interview QA to practice and prepare.

iOS app development has come a long way since the first iPhone was released in 2007. Today, there are millions of iOS apps available for download on the App Store, and developers are constantly looking for ways to create more efficient, scalable, and maintainable applications. One approach to achieving this is by leveraging the Gang of Four (GoF) design patterns.

The GoF design patterns, which were introduced in the book “Design Patterns: Elements of Reusable Object-Oriented Software,” provide a set of solutions to common software design problems.

What is GoF?

The “Gang of Four” (GoF) is a term used to refer to the four authors of the book “Design Patterns: Elements of Reusable Object-Oriented Software,” which is a seminal work in the field of software engineering.

The four authors are Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, and they are all experts in the field of object-oriented design and programming. The book, which was published in 1994, presents 23 design patterns that are commonly used in software development. These patterns describe solutions to recurring problems that arise in software design, and they help developers create flexible and maintainable software.

The Gang of Four’s book has had a significant impact on the field of software engineering, and it is considered a classic work in the industry. The term “Gang of Four” is often used to refer to the authors themselves, as well as to the book and its contents.

Some of the design patterns discussed in the book, such as the Singleton pattern and the Factory pattern, are commonly used in iOS development. The Singleton pattern, for example, can be used to ensure that only one instance of a class is created, which can be useful in scenarios where multiple instances of a class would cause problems. The Factory pattern, on the other hand, can be used to encapsulate the creation of objects and provide a flexible way to create different types of objects without having to change the code that uses them.

What are the benefits of GoF in iOS app?

In the context of iOS development, using these patterns can bring a range of benefits, including:

  1. Improved code readability: The GoF design patterns provide a standard language for describing and implementing object-oriented software systems. By using these patterns in your iOS app development, you can make your code more consistent and easier to read and understand for yourself and other developers.
  2. Increased maintainability: One of the primary benefits of using design patterns is that they make your code more maintainable over time. By using patterns like the Factory pattern, for example, you can encapsulate the creation of objects, making it easier to update or replace them later without changing the code that uses them.
  3. Faster development: Because the GoF patterns are widely known and documented, using them can speed up the development process. Rather than having to create new solutions from scratch, you can draw on existing patterns and best practices, which can help you work more efficiently and effectively.
  4. Scalability: By using patterns like the Observer pattern or the Strategy pattern, you can create iOS apps that are more scalable and adaptable to changing requirements. These patterns provide flexible ways to handle complex application logic, making it easier to extend or modify your app over time.
  5. Robustness: GoF design patterns can help you create more robust iOS apps. By using patterns like the Singleton pattern or the Decorator pattern, you can ensure that your app’s functionality is consistent and reliable, even in the face of unexpected or changing conditions.

GoF Patterns

The 23 Gang of Four (GoF) design patterns, as described in the book “Design Patterns: Elements of Reusable Object-Oriented Software,” are as follows:

  1. Creational Patterns
  • Abstract Factory
  • Builder
  • Factory Method
  • Prototype
  • Singleton
  1. Structural Patterns
  • Adapter
  • Bridge
  • Composite
  • Decorator
  • Facade
  • Flyweight
  • Proxy
  1. Behavioral Patterns
  • Chain of Responsibility
  • Command
  • Interpreter
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Template Method
  • Visitor

Each pattern represents a solution to a common design problem and can be applied to object-oriented software development across a range of programming languages and platforms, including iOS development. By using these patterns, developers can create software systems that are more efficient, maintainable, and scalable.

In this article, I will give some insight of three most commonly used patterns: Factory Method, Facade, Observer

Factory Method

The Factory Method pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. This pattern is useful when a class cannot anticipate the type of objects it needs to create or when a class wants to delegate the responsibility of object creation to its subclasses.

In the context of iOS development, the Factory Method pattern can be useful for encapsulating the creation of objects and providing a flexible way to create different types of objects without having to change the code that uses them. Here’s an example of how the Factory Method pattern can be used in an iOS app:

Suppose you are creating an app that displays different types of charts, such as bar charts, line charts, and pie charts. You could create a Chart superclass that defines the basic properties and methods that all chart types share, such as the data to be displayed, the chart size, and the chart color.

Next, you could create a ChartFactory class that defines a method for creating different types of charts, based on user input or other factors. For example, the factory might have methods like createBarChart(), createLineChart(), and createPieChart().

Each of these methods would return a different subclass of Chart, depending on the type of chart to be displayed. The subclasses might implement different algorithms for rendering the charts or have different properties that are specific to their chart types.

Finally, in the code that uses the charts, you could simply call the appropriate method on the ChartFactory to create the desired chart object, without having to worry about the specific implementation details of each chart type.

For example, you might have a method in your main view controller that looks something like this:

 func displayChart(type: String) {
    let chart = ChartFactory.createChart(type: type)
    self.view.addSubview(chart)
}

Here, the displayChart() method takes a string argument that specifies the type of chart to be displayed. It then calls the appropriate method on the ChartFactory to create the chart object, which is then added to the view.

By using the Factory Method pattern in this way, you can create a flexible, extensible, and maintainable system for creating different types of charts in your iOS app, without having to rewrite large portions of your code every time you want to add a new chart type.

Facade

The Facade pattern is a structural design pattern that provides a simplified interface to a complex system of classes, making it easier to use and reducing the amount of code required to interact with it. The Facade pattern is often used to provide a unified interface to a set of interfaces in a subsystem, making it easier to use for the client code that needs to interact with it.

In the context of iOS development, the Facade pattern can be used to simplify the complexity of a large system by creating a simpler and more user-friendly interface that hides the implementation details from the client. This can make it easier for developers to work with complex systems and improve the overall architecture of the application.

For example, let’s say you’re building an iOS app that interacts with a complex third-party API. This API has multiple endpoints, each with its own set of parameters and options, and requires a significant amount of boilerplate code to set up and use correctly.

To simplify this complexity, you could create a Facade class that provides a simplified interface to the API, hiding the implementation details and making it easier for the client code to interact with the API. This Facade class might have methods that encapsulate the various API endpoints, as well as additional methods for handling authentication, error handling, and other common tasks.

Here’s an example of how this might work:

class APIFacade {
    
    private let apiClient = APIClient()
    
    func searchForItems(query: String, completion: @escaping (Result<[Item], Error>) -> Void) {
        let parameters = ["q": query]
        apiClient.performRequest(endpoint: "search", parameters: parameters) { result in
            // Parse the response and return the results
            completion(result)
        }
    }
    
    func getItemDetails(id: Int, completion: @escaping (Result<ItemDetails, Error>) -> Void) {
        let parameters = ["id": "\(id)"]
        apiClient.performRequest(endpoint: "item-details", parameters: parameters) { result in
            // Parse the response and return the item details
            completion(result)
        }
    }
    
    // Additional methods for authentication, error handling, etc.
}

In this example, the APIFacade class provides two methods that encapsulate two different API endpoints: searchForItems() and getItemDetails(). These methods take care of setting up the necessary parameters and making the API request, and return the results in a simplified form.

The client code that needs to interact with the API can now simply create an instance of the APIFacade class and call the appropriate methods, without having to worry about the implementation details of the API or the boilerplate code required to set it up.

By using the Facade pattern in this way, you can simplify the complexity of a large system and make it easier to work with and maintain over time.

Observer

The Observer pattern is a behavioral design pattern that allows an object to notify a set of interested observers when its internal state changes. This pattern defines a one-to-many relationship between objects, where changes to one object are automatically communicated to all its dependent observers.

In the context of iOS development, the Observer pattern can be used to simplify communication between different parts of an application and to reduce dependencies between them. This pattern is especially useful when you have objects that need to be updated or react to changes in the state of another object, such as in user interface elements.

Here’s an example of how the Observer pattern might be used in an iOS app:

Suppose you have a view controller that displays a list of items and allows the user to add, edit, or delete items from the list. You also have a data model object that holds the current list of items, which can be modified by the user or by other parts of the app.

To keep the view controller in sync with the data model object, you could use the Observer pattern. Specifically, you could define a protocol that the data model object implements, which notifies the view controller whenever the list of items changes:

protocol ItemListObserver: AnyObject {
    func itemListDidChange()
}

class ItemListModel {
    var items: [Item] = [] {
        didSet {
            notifyObservers()
        }
    }
    
    private var observers: [ItemListObserver] = []
    
    func addObserver(_ observer: ItemListObserver) {
        observers.append(observer)
    }
    
    func removeObserver(_ observer: ItemListObserver) {
        if let index = observers.firstIndex(where: { $0 === observer }) {
            observers.remove(at: index)
        }
    }
    
    private func notifyObservers() {
        observers.forEach { $0.itemListDidChange() }
    }
}

In this example, the ItemListModel class defines an array of items and a set of methods for managing observers. Whenever the list of items changes, the model notifies all its observers by calling the itemListDidChange() method on each observer.

The view controller that displays the list of items can then implement the ItemListObserver protocol and register itself as an observer of the ItemListModel:

class ItemListViewController: UIViewController, ItemListObserver {
    
    let itemModel = ItemListModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        itemModel.addObserver(self)
    }
    
    func itemListDidChange() {
        // Update the table view to display the new list of items
        tableView.reloadData()
    }
    
    // Other methods for displaying and updating the list of items
}

In this example, the ItemListViewController implements the itemListDidChange() method, which is called by the ItemListModel whenever the list of items changes. The view controller can then update its user interface to display the new list of items.

By using the Observer pattern in this way, you can create a loosely coupled architecture that makes it easier to update different parts of the application independently of each other. This can make your code more maintainable and extensible over time.



✍️ Written by Ishtiak Ahmed

👉 Follow me on XLinkedIn



Get Ready to Shine: Mastering the iOS Interview




Enjoying the articles? Get the inside scoop by subscribing to my newsletter.

Get access to exclusive iOS development tips, tricks, and insights when you subscribe to my newsletter. You'll also receive links to new articles, app development ideas, and an interview preparation mini book.

If you know someone who would benefit from reading this article, please share it with them.