Adapter – Design Patterns in Swift

Introduction

The Adapter pattern is a structural design pattern and pretty common in Swift code. And it is very often used in systems based on some legacy code and makes legacy code work with modern classes. In addition, this pattern is recognizable by a constructor which takes an instance of a different abstract/interface type. When the adapter receives a call to any of its methods, it translates parameters to the appropriate format and then directs the call to one or several methods of the wrapped object.

Adapter Pattern – UML

Use Adapter Pattern in Swift

The Target defines the domain-specific interface used by the client code.

import XCTest

class Target {

    func request() -> String {
        return "Target: The default target's behavior."
    }
}

The Adaptee contains some useful behavior and its interface is incompatible with the existing client code. Therefore, we need some adaptation for 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))
    }
}

Conclusion

In summary, the key idea in this pattern is to work through a separate adapter that adapts the interface of an (already existing) class without changing it. In other words, this pattern allows otherwise incompatible classes to work together by converting the interface of one class into an interface expected by the clients. Besides, it catches calls for one object and transforms them to format and interface recognizable by the second object.

Leave a Reply

Your email address will not be published. Required fields are marked *