Facade Pattern in Swift

Dive into the world of structural design patterns as we demystify how Facade simplifies complex subsystems, provides unified interfaces, and enhances code maintainability

Understanding the Facade Design Pattern

The Facade pattern is a structural design pattern that provides a unified interface to a set of interfaces in a subsystem. It encapsulates a group of interfaces into a single interface, making it easier to use and understand. This pattern promotes loose coupling between subsystems, allowing changes in one part of the system to have minimal impact on other parts.

For example, without Facade Pattern. Our system looks like:

With Facade Pattern. Our system will look like:

Go to more details:

When to Use Facade

The Facade pattern is particularly useful in scenarios where a system is complex or has multiple subsystems. Here are some common situations where you might consider using the Facade pattern:

  1. Simplifying Complex Systems: Often, subsystems get more complex over time. A facade can simplify the interactions between these subsystems.

  2. Providing a Unified Interface: When you want to provide a simplified and unified interface to a set of interfaces or classes, the Facade pattern can act as a gateway, hiding the complexities.

  3. Reducing Coupling: Create facades to define entry points to each level of a subsystem. You can reduce coupling between multiple subsystems by requiring them to communicate only through facades. This makes it easier to manage changes and updates.

Implementing Facade

Let’s consider a practical example to understand how you can implement the Facade pattern in iOS development.

Suppose you have a multimedia subsystem with classes for authentication and user APIs. Now, let’s apply the Facade pattern:

Here is an example of the authentication API:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
protocol AuthAPI {
  func signIn(username: String, password: String) async throws
  func signUp(username: String, password: String) async throws
  func signOut() async throws
}

class DefaultAuthAPI: AuthAPI {
  func signIn(username: String, password: String) async throws {
    print("Sign in, \(username)!")
  }
  
  func signUp(username: String, password: String) async throws {
    print("Sign up, \(username)!")
  }
  
  func signOut() async throws {
    print("Sign out!")
  }
}

And the User API to retrieve user context after a successful sign-in:

1
2
3
4
5
6
7
8
9
protocol UserAPI {
  func getUserInfo() async throws
}

class DefaultUserAPI: UserAPI {
  func getUserInfo() async throws {
    print("Get user info from server!")
  }
}

Instead of directly interacting with these classes, you can create a facade that encapsulates the functionality and provides a simple interface for the client code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import Swinject

enum ServiceFacade {
  private static let container = Container()
  private static let threadSafeContainer: Resolver = container.synchronize()
  
  /// Register dependency injection here
  static func initializeContainer() {
    registerShared(AuthAPI.self) { _ in
      DefaultAuthAPI()
    }
    registerShared(UserAPI.self) { _ in
      DefaultUserAPI()
    }
  }
}

extension ServiceFacade {
  static func getService<T>(_ type: T.Type, name: String? = nil) -> T {
    return threadSafeContainer.getService(type, name: name)
  }
  
  @discardableResult
  private static func registerShared<T>(
    _ type: T.Type,
    name: String? = nil,
    factory: @escaping (Resolver) -> T
  ) -> ServiceEntry<T> {
    return container
      .register(type, name: name, factory: factory)
      .inObjectScope(.container)
  }
}

fileprivate extension Resolver {
  func getService<T>(_ type: T.Type, name: String? = nil) -> T {
    guard let service = resolve(type, name: name) else {
      fatalError("Service \(type) is NOT registered")
    }
    return service
  }
}

After some setup steps, you can easily use AuthAPI and UserAPI:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class LoginViewModel: ObservableObject {
  private let authAPI: AuthAPI
  private let userAPI: UserAPI
  
  init(
    authAPI: AuthAPI = ServiceFacade.getService(AuthAPI.self),
    userAPI: UserAPI = ServiceFacade.getService(UserAPI.self)
  ) {
    self.authAPI = authAPI
    self.userAPI = userAPI
  }

  func signIn() async {
    do {
      try await authAPI.signIn(username: "Kien Hoang", password: "123456")
      // Result: Sign in, Kien Hoang!
    } catch {
      // handle error here
    }
  }
}

In this example, the ServiceFacade class acts as a facade, hiding the complexities of AuthAPI, and UserAPI. The client code can now interact with the ServiceFacade instead of dealing with each subsystem individually.

Benefits of Using Facade

  1. Ease of Use: Clients only need to interact with the facade, reducing the need to understand the intricate details of each subsystem.

  2. Loose Coupling: By encapsulating subsystems, the Facade pattern promotes loose coupling, making it easier to make changes to individual subsystems without affecting the entire system.

  3. Improved Maintainability: Facades enhance code maintainability by providing a centralized point for managing interactions between subsystems.

Drawback of Using Facade

  1. A Huge Facade: A facade can become a god object coupled to all classes of an app.

Conclusion

  1. The Facade pattern proves invaluable when dealing with complex systems by providing a simplified interface for clients.
  2. It promotes the separation of concerns, reduces complexity, and enhances maintainability.
  3. However, it’s important to strike a balance between encapsulation and customization to ensure the pattern doesn’t hinder the system’s extensibility and flexibility.

Reference 🥳


Feel free to customize the content based on your preferences and any specific examples you’d like to include.

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy