Adapter pattern with Real Example
I won’t go into many theoretical parts of this design pattern as I believe many articles online already go into a lot of details.
An adapter pattern is a structural design pattern that you use when you want to decouple components that need to communicate somehow. The adapter “connects” the decoupled components without introducing coupling between them.
Or
The Adapter pattern is used to convert the interface of a component to the interface a client expects (without changing the client or the component). The Adapter keeps them decoupled from each other.
Or
You could also say that you introduce an adapter or a mapper in between the modules to literally take the output of one module and convert it to the expected input of another module so you decouple each module. This is very helpful when you want to decouple modules in a modular application.
These are all valid definitions of Adapter patterns now let’s take a look at an example.
I want to decouple my view controller from the API layer so let’s see how I could achieve that.
first, let’s see how my view controller loads posts from https://jsonplaceholder.typicode.com/posts looks with API coupling. The ViewController is coupled with the API layer ( Singleton APIClient). Imagine if you have a modularized app your VC knows the prominence of data meaning it knows the data comes from the API client so we have no way to reuse this with cached data or in-memory data.
class CoupledViewController:UICollectionViewController,UICollectionViewDelegateFlowLayout{
public init() {
let layout = UICollectionViewFlowLayout()
super.init(collectionViewLayout: layout)
}
fileprivate let cellId = "PostCell"
var posts = [Post]()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
self.collectionView.backgroundColor = .red
collectionView.register(PostCell.self, forCellWithReuseIdentifier: cellId)
collectionView.delegate = self
collectionView.dataSource = self
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
APIClient.shared.get(url: url) { data in
let root = try! JSONDecoder().decode(Root.self, from: data)
DispatchQueue.main.async{
self.posts = root.posts
self.collectionView.reloadData()
}
}
}
}
extension CoupledViewController {
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! PostCell
let post = posts[indexPath.row]
cell.configure(post: post)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: self.view.frame.width, height: 50)
}
}
How do we solve this?
we use an adapter pattern to decouple the API layer and the UI Layer and manage communication between them without both components knowing about each other.
`DecoupledPostsViewController
` has an abstraction PostsLoader which is in simple words telling thatDecoupledPostsViewController
is saying I need a load method and whoever provides me that I don’t care.
protocol PostsLoader{
func load(completion: @escaping ([Post]) -> Void)
}
class DecoupledPostsViewController:UICollectionViewController,UICollectionViewDelegateFlowLayout{
let loader:PostsLoader
public init(loader:PostsLoader) {
self.loader = loader
let layout = UICollectionViewFlowLayout()
super.init(collectionViewLayout: layout)
}
fileprivate let cellId = "PostCell"
var posts = [Post]()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
self.collectionView.backgroundColor = .red
collectionView.register(PostCell.self, forCellWithReuseIdentifier: cellId)
collectionView.delegate = self
collectionView.dataSource = self
loader.load{ Posts in
DispatchQueue.main.async{
self.posts = Posts
self.collectionView.reloadData()
}
}
}
}
extension DecoupledPostsViewController {
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! PostCell
let post = posts[indexPath.row]
cell.configure(post: post)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: self.view.frame.width, height: 50)
}
}
so in our case, the Adapter will adapt the communication between the API layer and the UI Layer.
Data -> Adapter(Converts Data to Array of Post) ->[Post] which can also be thought of in modular terms as API -> Adapter -> UI.
as you can see the adapter implements the PostsLoader
abstraction and takes in the apiClient
as dependency and converts data to [Posts] so basically the adapter literally takes the output of API module which is Data
and convert it to the expected input of the UI module which is [Post].
import Foundation
class PostsLoadAdapter:PostsLoader{
let apiClient:APIClient
init(apiClient: APIClient ) {
self.apiClient = apiClient
}
func load(completion: @escaping ([Post]) -> Void) {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
apiClient.get(url: url) { data in
let root = try! JSONDecoder().decode(Root.self, from: data)
completion(root.posts)
}
}
}
the viewController only needs [Posts]
it doesn’t matter where it comes from so we can get our data from the cache or anywhere else it doesn’t matter and the Adapters basically gets the data from the API module and give it to the UI without the UI Or Api module knowing about each other which is the beauty of the Adapter pattern.
Adapter pattern is also used to decouple 3rd party frameworks from your codebase which i will write in detail in another article.
Github:https://github.com/abdahad1996/Adapter_Pattern