Adapter is a structural design pattern, which allows incompatible objects to collaborate.
The Adapter acts as a wrapper between two objects. It catches calls for one object and transforms them to format and interface recognizable by the second object.
Conceptual Example
This example illustrates the structure of the Adapter design pattern and focuses on the following questions:
What classes does it consist of?
What roles do these classes play?
In what way the elements of the pattern are related?
After learning about the pattern’s structure it’ll be easier for you to grasp the following example, based on a real-world Swift use case.
Example.swift: Conceptual example
import XCTest
/// The Target defines the domain-specific interface used by the client code.
class Target {
func request() -> String {
return "Target: The default target's behavior."
}
}
/// The Adaptee contains some useful behavior, but its interface is incompatible
/// with the existing client code. The Adaptee needs some adaptation before the
/// client code can use it.
class Adaptee {
public func specificRequest() -> String {
return ".eetpadA eht fo roivaheb laicepS"
}
}
/// The Adapter makes the Adaptee's interface compatible with the Target's
/// interface.
class Adapter: Target {
private var adaptee: Adaptee
init(_ adaptee: Adaptee) {
self.adaptee = adaptee
}
override func request() -> String {
return "Adapter: (TRANSLATED) " + adaptee.specificRequest().reversed()
}
}
/// The client code supports all classes that follow the Target interface.
class Client {
// ...
static func someClientCode(target: Target) {
print(target.request())
}
// ...
}
/// Let's see how it all works together.
class AdapterConceptual: XCTestCase {
func testAdapterConceptual() {
print("Client: I can work just fine with the Target objects:")
Client.someClientCode(target: Target())
let adaptee = Adaptee()
print("Client: The Adaptee class has a weird interface. See, I don't understand it:")
print("Adaptee: " + adaptee.specificRequest())
print("Client: But I can work with it via the Adapter:")
Client.someClientCode(target: Adapter(adaptee))
}
}
Output.txt: Execution result
Client: I can work just fine with the Target objects:
Target: The default target's behavior.
Client: The Adaptee class has a weird interface. See, I don't understand it:
Adaptee: .eetpadA eht fo roivaheb laicepS
Client: But I can work with it via the Adapter:
Adapter: (TRANSLATED) Special behavior of the Adaptee.
Real World Example
Example.swift: Real world example
import XCTest
import UIKit
/// Adapter Design Pattern
///
/// Intent: Convert the interface of a class into the interface clients expect.
/// Adapter lets classes work together that couldn't work otherwise because of
/// incompatible interfaces.
class AdapterRealWorld: XCTestCase {
/// Example. Let's assume that our app perfectly works with Facebook
/// authorization. However, users ask you to add sign in via Twitter.
///
/// Unfortunately, Twitter SDK has a different authorization method.
///
/// Firstly, you have to create the new protocol 'AuthService' and insert
/// the authorization method of Facebook SDK.
///
/// Secondly, write an extension for Twitter SDK and implement methods of
/// AuthService protocol, just a simple redirect.
///
/// Thirdly, write an extension for Facebook SDK. You should not write any
/// code at this point as methods already implemented by Facebook SDK.
///
/// It just tells a compiler that both SDKs have the same interface.
func testAdapterRealWorld() {
print("Starting an authorization via Facebook")
startAuthorization(with: FacebookAuthSDK())
print("Starting an authorization via Twitter.")
startAuthorization(with: TwitterAuthSDK())
}
func startAuthorization(with service: AuthService) {
/// The current top view controller of the app
let topViewController = UIViewController()
service.presentAuthFlow(from: topViewController)
}
}
protocol AuthService {
func presentAuthFlow(from viewController: UIViewController)
}
class FacebookAuthSDK {
func presentAuthFlow(from viewController: UIViewController) {
/// Call SDK methods and pass a view controller
print("Facebook WebView has been shown.")
}
}
class TwitterAuthSDK {
func startAuthorization(with viewController: UIViewController) {
/// Call SDK methods and pass a view controller
print("Twitter WebView has been shown. Users will be happy :)")
}
}
extension TwitterAuthSDK: AuthService {
/// This is an adapter
///
/// Yeah, we are able to not create another class and just extend an
/// existing one
func presentAuthFlow(from viewController: UIViewController) {
print("The Adapter is called! Redirecting to the original method...")
self.startAuthorization(with: viewController)
}
}
extension FacebookAuthSDK: AuthService {
/// This extension just tells a compiler that both SDKs have the same
/// interface.
}
Output.txt: Execution result
Starting an authorization via Facebook
Facebook WebView has been shown
///
Starting an authorization via Twitter
The Adapter is called! Redirecting to the original method...
Twitter WebView has been shown. Users will be happy :)