Greetings, traveler!
We continue to learn about Design Patterns. Next, we will look at Structural Patterns, starting with the Adapter Pattern. What is the purpose of this pattern? It is used when two different entities need to communicate with each other, but their interfaces are not compatible. The Adapter pattern handles this by bridging the two entities, allowing them to interact seamlessly.
Example of usage
Let’s simplify this with an example. Imagine we have an application that receives posts from a server and displays their content. The issue arises when the user interface and the ViewModel expect a slightly different structure than what the service provides.
// MARK: - Models
struct Post {
let title: String
let content: String
let tags: [String]
let categories: [String]
}
struct RefinedPost {
let title: String
let content: String
}
// MARK: - API Service
final class PostAPIService {
func getPost(_ completion: @escaping (Result<Post, Error>) -> Void) {
let post = Post(
title: "Post Title",
content: "Post Content",
tags: [],
categories: []
)
completion(.success(post))
}
}
// MARK: - ViewModel
final class ViewModel {
var completion: ((RefinedPost) -> Void)?
func update() {
let post: RefinedPost = .init(title: "", content: "")
completion?(post)
}
}
To bridge this gap, we can create an adapter. This adapter will seamlessly adapt the service’s interface to the required specifications, allowing the ViewModel to obtain the data it needs, without any additional effort or knowledge of what is happening behind the scenes.
final class PostServiceAdapter: PostManager {
private let apiService = PostAPIService()
func getPost(_ completion: @escaping (Result<RefinedPost, Error>) -> Void) {
apiService.getPost { result in
switch result {
case .success(let post):
completion(.success(.init(title: post.title, content: post.content)))
case .failure(let error):
completion(.failure(error))
}
}
}
}
final class ViewModel {
var completion: ((RefinedPost) -> Void)?
private let manager: PostManager
init(manager: PostManager) {
self.manager = manager
}
func update() {
manager.getPost { [weak self] result in
switch result {
case .success(let post):
self?.completion?(post)
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
Conclusion
That’s all for now. We’ve just introduced a powerful tool in your software development arsenal — the Adapter pattern. It’s a simple yet effective way to share responsibilities between entities, helping you organize your code and make it more maintainable. In our next article, we’ll delve deeper into Structural Design Patterns and explore the Bridge Pattern.
Check out other posts in the Design Patterns series:
- Visitor Design Pattern in Swift
- Themplate Method Design Pattern in Swift
- Strategy Design Pattern in Swift
- State Design Pattern in Swift
- Observer Design Pattern in Swift
- Memento Design Pattern in Swift
- Mediator Design Pattern in Swift
- Iterator Design Pattern in Swift
- Command Design Pattern in Swift
- Chain of Responsibility Design Pattern in Swift
- Proxy Design Pattern in Swift
- FlyWeight Design Pattern in Swift
- Facade Design Pattern in Swift
- Decorator Design Pattern in Swift
- Composite Design Pattern in Swift
- Bridge Design Pattern in Swift
- Singleton Design Pattern in Swift
- Prototype Design Pattern in Swift
- Builder Design Pattern in Swift
- Abstract Factory Design Pattern in Swift
- Factory Method Design Pattern in Swift
- Design Patterns: Basics