Introduction
The Facade design pattern serves as a front-facing interface for a complex set of classes, a library, or a framework. This article will explain its applications in iOS development and the types of problems it can solve.
Table of contents
Definition
The Facade design pattern is a structural pattern that provides a simplified interface to a framework, library, or a set of related classes.
What kind of problems can the Facade design pattern solve?
Complexity of subsystems. Large systems often consist of multiple interconnected classes and components, making them difficult to use and understand. A client that interacts directly with multiple components may require extensive knowledge of their implementation details.
Tight coupling. When clients are directly dependent on multiple components, changes in the subsystem can lead to widespread modifications in client code, increasing maintenance costs.
Difficulties in migration or refactoring. If a system's implementation changes, all clients interacting with its components must be updated accordingly, leading to high development effort.
What solutions does the Facade design pattern describe?
Provide a unified interface. The Facade design pattern introduces a higher-level interface that encapsulates complex interactions between multiple subsystem components, exposing a simple and easy-to-use API.
Decouple the client from the subsystem. Clients only interact with the facade, reducing their dependencies on specific subsystem implementations. This improves maintainability and allows for modifications to the underlying system.
Encapsulate complexity. The facade acts as a mediator, handling interactions between subsystem components internally, so clients do not need to manage multiple dependencies.
UML Diagram
The Client
does not access the subsystem components directly. Instead, the Client
works through a Facade
class that provides a simplified interface. The Client
depends only on the Facade interface and doesn't know anything about the subsystem components or their relationships.
Examples
Let's imagine that the system has three classes:
-
AuthManager
is responsible for the authentication process and handling all tasks related to authentication. For the sake of simplicity, it exposes only a single method:login(username:password)
. -
PaymentProcessor
is responsible for processing a payment transaction. -
NotificationService
is responsible for sending a notification to observers that the order has been placed.
If a client wants to work with these objects, it would need to communicate with each object directly, which leads to the problems described above. It's better to create a facade object that serves as a front-facing interface for the client and hides the communication complexity between objects.
protocol IAuthManager {
func login(username: String, password: String)
}
final class AuthManager: IAuthManager {
func login(username: String, password: String) {
print("User logged in")
}
}
protocol IPaymentProcessor {
func processPayment(amount: Double)
}
final class PaymentProcessor: IPaymentProcessor {
func processPayment(amount: Double) { print("Payment processed: $\(amount)") }
}
protocol INotificationService {
func sendNotification(message: String)
}
final class NotificationService: INotificationService {
func sendNotification(message: String) { print("Notification sent: \(message)") }
}
Define an IECommerceFacade
protocol that exposes a single method for placing an order, and an ECommerceFacade
class that implements this protocol and sets up communication between subsystem objects, which are hidden from the client.
protocol IECommerceFacade {
func placeOrder(username: String, password: String, amount: Double)
}
final class ECommerceFacade: IECommerceFacade {
private let authManager: IAuthManager
private let paymentProcessor: IPaymentProcessor
private let notificationService: INotificationService
init(authManager: IAuthManager, paymentProcessor: IPaymentProcessor, notificationService: INotificationService) {
self.authManager = authManager
self.paymentProcessor = paymentProcessor
self.notificationService = notificationService
}
func placeOrder(username: String, password: String, amount: Double) {
authManager.login(username: username, password: password)
paymentProcessor.processPayment(amount: amount)
notificationService.sendNotification(message: "Your order has been placed.")
}
}
let authManager = AuthManager()
let paymentProcessor = PaymentProcessor()
let noticationService = NotificationService()
let ecommerceFacade = ECommerceFacade(
authManager: authManager,
paymentProcessor: paymentProcessor,
notificationService: notificationService
)
ecommerceFacade.placeOrder(username: "JohnDoe", password: "1234", amount: 99.99)
This example demonstrates the Facade pattern by providing a single ECommerceFacade
class that simplifies the process of placing an order, while internally managing authentication, payment, and notifications.
Summary
The Facade design pattern simplifies complex systems by providing a unified interface that encapsulates interactions between multiple subsystem components. It solves problems related to system complexity, tight coupling, and high maintenance costs by decoupling clients from the internal workings of the system. This pattern improves code readability, maintainability, and flexibility.
By adopting the Facade pattern, developers can create modular, maintainable, and user-friendly systems, making it an essential tool in software design and architecture.
Top comments (0)