Facade Design pattern -swift- Examples
The Facade pattern is a design pattern that provides a simplified interface to a complex system. The goal of the pattern is to make it easier to use and understand the system by hiding its complexity behind a simplified interface.
The Facade pattern achieves this by providing a single interface to a set of subsystems, rather than exposing the subsystems directly to client code. The Facade class acts as a mediator between client code and the subsystems, providing a simplified interface that encapsulates the details of the subsystems.
By using the Facade pattern, client code can interact with the system without needing to know the details of its subsystems. This makes the system easier to use and understand, as client code only needs to interact with the Facade interface, rather than the entire system.
The Facade design pattern provides several benefits:
Simplified interface: The Facade pattern provides a simplified interface to a complex system, making it easier for clients to use the system without worrying about its internal details.
Decoupling: The Facade pattern helps to decouple the client code from the complex system, reducing the dependencies and making the code easier to maintain and test.
Encapsulation: The Facade pattern encapsulates the complex system behind a single class, providing a layer of abstraction that can be used to protect the system’s internal implementation details.
Flexibility: The Facade pattern allows for flexibility in the design of the system by providing a layer of abstraction that can be modified without affecting the client code.
Code reusability: The Facade pattern promotes code reusability by providing a simple and reusable interface that can be used by different clients to access the same complex system.
Improved performance: The Facade pattern can improve performance by optimizing the interactions between the client code and the complex system, reducing the number of unnecessary calls and data transfers.
Here are different examples to learn Facade Design Pattern:-
class EmailAuthenticationSystem {
public bool Authenticate(string username, string password) {
// Authenticate user using provided credentials
return true; // Return true if authentication succeeds
}
}
class EmailCompositionSystem {
public string ComposeMessage(string recipient, string subject, string body) {
// Compose message with provided recipient, subject, and body
return "Email message"; // Return the composed email message
}
}
class EmailSendingSystem {
public void SendEmail(string recipient, string message) {
// Send email message to provided recipient
Console.WriteLine("Email sent to " + recipient + ": " + message);
}
}
class EmailService {
private EmailAuthenticationSystem authSystem;
private EmailCompositionSystem compSystem;
private EmailSendingSystem sendSystem;
public EmailService() {
authSystem = new EmailAuthenticationSystem();
compSystem = new EmailCompositionSystem();
sendSystem = new EmailSendingSystem();
}
public bool Authenticate(string username, string password) {
return authSystem.Authenticate(username, password);
}
public void SendEmail(string recipient, string subject, string body) {
if (Authenticate("user123", "password")) { // Authenticate user before sending email
string message = compSystem.ComposeMessage(recipient, subject, body); // Compose email message
sendSystem.SendEmail(recipient, message); // Send email message
}
}
}
class Client {
static void Main(string[] args) {
EmailService emailService = new EmailService();
// Send email
emailService.SendEmail("recipient@example.com", "Subject", "Body");
}
}
Overall, the Facade pattern is a powerful design pattern that can improve the usability and maintainability of software systems by providing a simplified interface to complex subsystems, and by decoupling client code from implementation details.
// Define the subsystems
class PaymentSubsystem {
func processPayment(amount: Double) {
print("Payment processed for amount: \(amount)")
}
}
class OrderSubsystem {
func placeOrder(items: [String]) {
print("Order placed with items: \(items)")
}
}
class CustomerSubsystem {
func addCustomer(name: String, email: String) {
print("Customer added with name: \(name) and email: \(email)")
}
}
// Define the facade
class OnlineStore {
let paymentSubsystem = PaymentSubsystem()
let orderSubsystem = OrderSubsystem()
let customerSubsystem = CustomerSubsystem()
func checkout(items: [String], amount: Double, name: String, email: String) {
paymentSubsystem.processPayment(amount: amount)
orderSubsystem.placeOrder(items: items)
customerSubsystem.addCustomer(name: name, email: email)
}
}
// Example usage
let onlineStore = OnlineStore()
onlineStore.checkout(items: ["Shirt", "Pants"], amount: 100.0, name: "John Doe", email: "johndoe@example.com")
// Define the API service endpoints
class UserService {
func getUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
// implementation of getUser endpoint
}
}
class ProductService {
func getProduct(id: Int, completion: @escaping (Result<Product, Error>) -> Void) {
// implementation of getProduct endpoint
}
}
class OrderService {
func placeOrder(productIds: [Int], userId: Int, completion: @escaping (Result<Order, Error>) -> Void) {
// implementation of placeOrder endpoint
}
}
// Define the facade
class OnlineStore {
let userService = UserService()
let productService = ProductService()
let orderService = OrderService()
func getUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
userService.getUser(id: id, completion: completion)
}
func getProduct(id: Int, completion: @escaping (Result<Product, Error>) -> Void) {
productService.getProduct(id: id, completion: completion)
}
func placeOrder(productIds: [Int], userId: Int, completion: @escaping (Result<Order, Error>) -> Void) {
orderService.placeOrder(productIds: productIds, userId: userId, completion: completion)
}
}
// Example usage
let onlineStore = OnlineStore()
onlineStore.getUser(id: 1) { result in
// handle result of getUser
}
onlineStore.getProduct(id: 2) { result in
// handle result of getProduct
}
onlineStore.placeOrder(productIds: [3, 4], userId: 5) { result in
// handle result of placeOrder
}
class MyView: UIView {
private let titleLabel = UILabel()
private let descriptionLabel = UILabel()
private let imageView = UIImageView()
init(title: String, description: String, image: UIImage) {
super.init(frame: .zero)
setupTitleLabel(title)
setupDescriptionLabel(description)
setupImageView(image)
// setup constraints for subviews
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: self.topAnchor),
titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),
descriptionLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor),
descriptionLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
imageView.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor),
imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
private func setupTitleLabel(_ title: String) {
titleLabel.text = title
titleLabel.font = UIFont.boldSystemFont(ofSize: 20)
titleLabel.numberOfLines = 0
titleLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(titleLabel)
}
private func setupDescriptionLabel(_ description: String) {
descriptionLabel.text = description
descriptionLabel.font = UIFont.systemFont(ofSize: 16)
descriptionLabel.numberOfLines = 0
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(descriptionLabel)
}
private func setupImageView(_ image: UIImage) {
imageView.image = image
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imageView)
}
func update(title: String?, description: String?, image: UIImage?) {
if let newTitle = title {
titleLabel.text = newTitle
}
if let newDescription = description {
descriptionLabel.text = newDescription
}
if let newImage = image {
imageView.image = newImage
}
}
}
By using the Facade pattern in this way, we are able to provide a simplified UIView for the client code that abstracts away the complexity of interacting with the subviews and layout, and makes it easier to use the custom view as a whole.
class RootViewController: UIViewController {
private let loginViewController = LoginViewController()
private let homeViewController = HomeViewController()
override func viewDidLoad() {
super.viewDidLoad()
// setup subviews
addChild(loginViewController)
view.addSubview(loginViewController.view)
loginViewController.didMove(toParent: self)
addChild(homeViewController)
view.addSubview(homeViewController.view)
homeViewController.didMove(toParent: self)
// setup constraints for subviews
NSLayoutConstraint.activate([
loginViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
loginViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
loginViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
loginViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
homeViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
homeViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
homeViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
homeViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
// show login view controller
showLoginViewController()
}
private func showLoginViewController() {
loginViewController.view.isHidden = false
homeViewController.view.isHidden = true
}
private func showHomeViewController() {
loginViewController.view.isHidden = true
homeViewController.view.isHidden = false
}
func login() {
// perform login logic
// ...
// show home view controller
showHomeViewController()
}
func logout() {
// perform logout logic
// ...
// show login view controller
showLoginViewController()
}
}
By using the Facade pattern in this way, we are able to provide a simplified interface for the client code to interact with the login and home view controllers, and abstract away the complexity of managing the subview controllers and their views.
class MyViewModel {
private let apiService: ApiService
private let cacheService: CacheService
init(apiService: ApiService, cacheService: CacheService) {
self.apiService = apiService
self.cacheService = cacheService
}
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
if let cachedData = cacheService.getCachedData() {
completion(.success(cachedData))
} else {
apiService.fetchData { [weak self] result in
switch result {
case .success(let data):
self?.cacheService.cacheData(data)
completion(.success(data))
case .failure(let error):
completion(.failure(error))
}
}
}
}
}
class VideoEditor {
private let audioService: AudioService
private let videoService: VideoService
private let exportService: ExportService
init(audioService: AudioService, videoService: VideoService, exportService: ExportService) {
self.audioService = audioService
self.videoService = videoService
self.exportService = exportService
}
func exportVideo(from url: URL, with audio: Bool, completion: @escaping (Result<URL, Error>) -> Void) {
videoService.loadVideo(from: url) { [weak self] result in
switch result {
case .success(let videoAsset):
if audio {
self?.audioService.loadAudio { [weak self] result in
switch result {
case .success(let audioAsset):
self?.videoService.addAudio(to: videoAsset, with: audioAsset) { [weak self] result in
switch result {
case .success(let composedVideoAsset):
self?.exportService.exportVideo(asset: composedVideoAsset) { result in
completion(result)
}
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
}
} else {
self?.exportService.exportVideo(asset: videoAsset) { result in
completion(result)
}
}
case .failure(let error):
completion(.failure(error))
}
}
}
}