Facade Design Pattern in iOS

Omar Saibaa
9 min readNov 1, 2023

--

The facade design pattern is a software design pattern that provides a unified interface to a set of interfaces in a subsystem. Facades define a higher-level interface that makes the subsystem easier to use.

The facade design pattern is often used in iOS development to simplify the interaction with complex subsystems, such as the network layer, the database layer, and the view layer.

Example 1: 👶

Imagine you are going to a restaurant with your family. You don’t need to know how to cook all of the food on the menu, or how to clean up afterwards. You just need to know how to order your food and pay the bill.

The menu is the façade for the restaurant’s kitchen. It provides a simple interface to the complex system of cooks, ovens, and other equipment that makes the restaurant run.

Another analogy is to imagine a car. When you drive a car, you are not interacting directly with the engine, transmission, and other components. Instead, you are interacting with the facade, which is the steering wheel, pedals, and other controls.

The controls in the car then forward your requests to the appropriate subsystems in the car, such as the engine, transmission, and brakes.

Example 2: 🍎

Imagine you are developing an iOS app for a bank. The app needs to allow users to perform a variety of tasks, such as checking their balance, transferring money, and depositing checks.

Each of these tasks is implemented by a separate subsystem in the app. For example, there is a subsystem for checking balances, a subsystem for transferring money, and a subsystem for depositing checks.

The facade design pattern can be used to provide a simple interface to all of these subsystems. This would allow you to create a single class that exposes methods for performing all of the tasks that users need to be able to do.

For example, the facade class could have methods called checkBalance(), transferMoney(), and depositCheck(). When a user calls one of these methods, the facade would forward the request to the appropriate subsystem.

This would make it much easier for developers to use the app’s functionality, as they would not need to learn about all of the different subsystems and how to interact with them directly.

Example 3: 💻

Consider a simple iOS app that allows users to log in and view a list of items. The app has two subsystems:

  • A network layer that is responsible for making requests to a remote server
  • A database layer that is responsible for storing and retrieving data from the local database

To implement the app’s functionality, we would need to interact with both of these subsystems. For example, to log in a user, we would need to make a request to the remote server to authenticate the user’s credentials. To view a list of items, we would need to query the local database.

This could lead to code that is difficult to read and maintain. We would need to know about the specific interfaces of both subsystems and how to use them correctly.

Solution:

The facade design pattern solves this problem by providing a unified interface to the network and database layers. This allows us to interact with both subsystems without having to know about their specific interfaces.

To implement the facade design pattern, we would create a facade class that exposes methods for logging in users and viewing lists of items. The facade class would then forward these requests to the appropriate subsystems.

For example, the facade class might have a method called loginUser() that takes the user's credentials as input and returns a boolean indicating whether the login was successful. The facade class would then forward this request to the network layer.

The facade class might also have a method called getItems() that returns a list of items from the local database. The facade class would then forward this request to the database layer.

How to implement the Facade pattern in iOS?

  1. Identify the subsystems that you want to simplify access to.
  2. Create a facade class that defines a simplified interface to the subsystems.
  3. Implement the facade class by delegating requests to the appropriate subsystems.
  4. Use the facade class in your client code instead of the subsystems directly.
// Subsystem 1
protocol Subsystem1 {
func doSomething1()
}

// Subsystem 2
protocol Subsystem2 {
func doSomething2()
}

// Facade
class Facade {
private let subsystem1: Subsystem1
private let subsystem2: Subsystem2

init(subsystem1: Subsystem1, subsystem2: Subsystem2) {
self.subsystem1 = subsystem1
self.subsystem2 = subsystem2
}

func doSomething() {
subsystem1.doSomething1()
subsystem2.doSomething2()
}
}

// Client
let facade = Facade(subsystem1: Subsystem1Impl(), subsystem2: Subsystem2Impl())

facade.doSomething()

In this example, the Facade class provides a simplified interface to the Subsystem1 and Subsystem2 subsystems. The Facade class hides the implementation details of the subsystems and makes it easier for client code to interact with them.

To use the facade pattern in your own code, simply identify the subsystems that you want to simplify access to and create a facade class that defines a simplified interface to those subsystems. Then, use the facade class in your client code instead of the subsystems directly.

Example 4: 💪

Imagine you are developing an Amazon app for iOS. The app needs to allow users to browse products, add items to their cart, and checkout.

The app has three subsystems:

  • A product catalog subsystem that is responsible for retrieving information about products from the Amazon API
  • A shopping cart subsystem that is responsible for managing the user’s shopping cart
  • A checkout subsystem that is responsible for processing the user’s purchase

To implement the app’s functionality, you would need to interact with all three of these subsystems.
For example, to browse products, you would need to make a request to the product catalog subsystem.
To add an item to the cart, you would need to interact with the shopping cart subsystem.
And to checkout, you would need to interact with the checkout subsystem.

This could lead to code that is difficult to read and maintain. You would need to know about the specific interfaces of all three subsystems and how to use them correctly.

Solution:

The facade design pattern solves this problem by providing a unified interface to the product catalog, shopping cart, and checkout subsystems. This allows you to interact with all three subsystems without having to know about their specific interfaces.

To implement the facade design pattern, you would create a facade class that exposes methods for browsing products, adding items to the cart, and checking out. The facade class would then forward these requests to the appropriate subsystems.

For example, the facade class might have a method called browseProducts() that returns a list of products from the product catalog subsystem. The facade class might also have methods called addToCart() and checkout().

Code:

// Product catalog subsystem
protocol ProductCatalogSubsystem {
func getProducts() async throws -> [Product]
}

// Shopping cart subsystem
protocol ShoppingCartSubsystem {
func addItem(product: Product)
func removeItem(product: Product)
func getItems() -> [Product]
}

// Checkout subsystem
protocol CheckoutSubsystem {
func checkout(items: [Product]) async throws -> Order
}

// Facade class
class Facade {
private let productCatalogSubsystem: ProductCatalogSubsystem
private let shoppingCartSubsystem: ShoppingCartSubsystem
private let checkoutSubsystem: CheckoutSubsystem

init(productCatalogSubsystem: ProductCatalogSubsystem, shoppingCartSubsystem: ShoppingCartSubsystem, checkoutSubsystem: CheckoutSubsystem) {
self.productCatalogSubsystem = productCatalogSubsystem
self.shoppingCartSubsystem = shoppingCartSubsystem
self.checkoutSubsystem = checkoutSubsystem
}

func browseProducts() async throws -> [Product] {
return try await productCatalogSubsystem.getProducts()
}

func addToCart(product: Product) {
shoppingCartSubsystem.addItem(product: product)
}

func checkout() async throws -> Order {
return try await checkoutSubsystem.checkout(items: shoppingCartSubsystem.getItems())
}
}

// Usage
let facade = Facade(productCatalogSubsystem: ProductCatalogSubsystemImpl(), shoppingCartSubsystem: ShoppingCartSubsystemImpl(), checkoutSubsystem: CheckoutSubsystemImpl())

// Browse products
let products = try await facade.browseProducts()

// Add an item to the cart
facade.addToCart(product: products[0])

// Checkout
let order = try await facade.checkout()

In this example, the facade class provides a simplified interface for browsing products, adding items to the cart, and checking out. The facade class hides the implementation details of the product catalog, shopping cart, and checkout subsystems.

Example 5: 🐉

A Facebook app needs to interact with three different subsystems:

  • A network layer that is responsible for making requests to the Facebook API
  • A database layer that is responsible for storing and retrieving data from the local database
  • A view layer that is responsible for displaying information to the user

This could lead to code that is difficult to read and maintain. Developers would need to know about the specific interfaces of all three subsystems and how to use them correctly.

Solution:

The facade design pattern solves this problem by providing a unified interface to the network, database, and view layers. This allows developers to interact with all three subsystems without having to know about their specific interfaces.

To implement the facade design pattern, developers would create a facade class that exposes methods for viewing the feed, posting updates, and liking and commenting on other people’s posts. The facade class would then forward these requests to the appropriate subsystems.

Full Code:

// Network layer
protocol NetworkLayer {
func getFeed(completion: @escaping (Result<[Post], Error>) -> Void)
func postUpdate(text: String, completion: @escaping (Result<Post, Error>) -> Void)
func likePost(postID: Int, completion: @escaping (Result<Void, Error>) -> Void)
func commentOnPost(postID: Int, comment: String, completion: @escaping (Result<Void, Error>) -> Void)
}

// Database layer
protocol DatabaseLayer {
func saveFeed(posts: [Post])
func getFeed() -> [Post]
}

// View layer
protocol ViewLayer {
func displayFeed(posts: [Post])
func displayPostUpdated(post: Post)
func displayPostLiked(post: Post)
func displayPostCommentedOn(post: Post)
}

// Facade class
class Facade {
private let networkLayer: NetworkLayer
private let databaseLayer: DatabaseLayer
private let viewLayer: ViewLayer

init(networkLayer: NetworkLayer, databaseLayer: DatabaseLayer, viewLayer: ViewLayer) {
self.networkLayer = networkLayer
self.databaseLayer = databaseLayer
self.viewLayer = viewLayer
}

func getFeed() async throws -> [Post] {
try await networkLayer.getFeed()
}

func postUpdate(text: String) async throws -> Post {
try await networkLayer.postUpdate(text: text)
}

func likePost(postID: Int) async throws {
try await networkLayer.likePost(postID: postID)
}

func commentOnPost(postID: Int, comment: String) async throws {
try await networkLayer.commentOnPost(postID: postID, comment: comment)
}

func displayFeed(posts: [Post]) {
viewLayer.displayFeed(posts: posts)
}

func displayPostUpdated(post: Post) {
viewLayer.displayPostUpdated(post: post)
}

func displayPostLiked(post: Post) {
viewLayer.displayAlert(title: "Post liked!", message: "You have liked the post with the ID: \(post.id)")
}

func displayPostCommentedOn(post: Post) {
viewLayer.displayAlert(title: "Post commented on!", message: "You have commented on the post with the ID: \(post.id)")
}
}

// Usage
let facade = Facade(networkLayer: NetworkLayer(), databaseLayer: DatabaseLayer(), viewLayer: ViewLayer())

// Get the user's feed
let posts = try await facade.getFeed()

// Display the feed
facade.displayFeed(posts: posts)

// Post an update
let post = try await facade.postUpdate(text: "This is a new update!")

// Display the updated post
facade.displayPostUpdated(post: post)

// Like a post
try await facade.likePost(postID: 12345)

// Display the liked post
facade.displayPostLiked(post: post)

// Comment on a post
try await facade.commentOnPost(postID)

Advantages of the facade design pattern in iOS:

  • Simplified interface: The facade design pattern provides a simplified interface to a set of complex interfaces. This makes the system easier to use for developers.
  • Decoupling: The facade design pattern decouples the client code from the subsystem interfaces. This makes the code more reusable and maintainable.
  • Reduced complexity: The facade design pattern reduces the complexity of the client code by hiding the implementation details of the subsystems.
  • Improved performance: The facade design pattern can improve performance by caching the results of subsystem calls.

Disadvantages of the facade design pattern in iOS:

  • Increased complexity: The facade design pattern can introduce additional complexity to the system, especially if the facade class is large and complex.
  • Abstraction penalty: The facade design pattern introduces an abstraction penalty, which is the overhead of using the facade class instead of interacting with the subsystems directly.
  • Reduced flexibility: The facade design pattern can reduce the flexibility of the system by making it difficult to add new features or modify existing features.

Tips for using Facade design pattern in iOS:

  • Identify the subsystems that you want to simplify access to. The first step is to identify the subsystems that you want to simplify access to. These subsystems should be complex and difficult to use directly.
  • Define a simplified interface to the subsystems. Once you have identified the subsystems that you want to simplify access to, you need to define a simplified interface to those subsystems. This interface should be easy to use and understand.
  • Implement the facade class by delegating requests to the appropriate subsystems. The facade class should delegate requests to the appropriate subsystems. This will allow you to hide the implementation details of the subsystems from the client code.
  • Use the facade class in your client code instead of the subsystems directly. Once you have implemented the facade class, you should use it in your client code instead of the subsystems directly. This will make your code more reusable, maintainable, and easier to use.

Finally:

The facade design pattern is a powerful tool that can be used to simplify the use of complex systems in iOS. It provides a simplified interface to a set of complex interfaces, decouples the client code from the subsystem interfaces, reduces the complexity of the client code by hiding the implementation details of the subsystems, and can improve performance by caching the results of subsystem calls. It is important to weigh the advantages and disadvantages before using the facade design pattern in your code.

Thanks for reading and Don’t forget to claps, comment and follow me for more.

--

--