アダプターをSwift5で実装する


※この記事は「全デザインパターンをSwift5で実装する」https://qiita.com/satoru_pripara/items/3aa80dab8e80052796c6 の一部です。

The Adapter(アダプター)

0. アダプターの意義

レガシーコード(古いコード)や、他者製のコード(外部ライブラリなど)を使う際、自分の書いたコードと実装形式などが違う事がある。そういったコードを用いる際、いちいち自分のコードと適合する形に直さなければならないとすると非常に手間がかかる。

アダプターパターンはそういったコードを自分のコードから扱いやすい形に修正してくれるデザインパターンである。

元のコードがどういった実装をしていたか? などをいちいち意識する事なく自分のコードに適合させられるため、時間と労力の節約につながる。

注意点としては、外部のコード等がもともと期待する機能を持っていないのにアダプターパターンを使って無理に他のコードと統合しようとしてはいけないという事である。それはアダプターの能力を超えている。そういったケースは単純に外部のコードの選定ミスなので、違うライブラリなど目的にあったコードを選ぶようにする。

1.アダプターを用いない場合

  • プロトコルPaymentGatewayに準拠している自前のクラスPayPalStripe
Payment.swift
import Foundation

public protocol PaymentGateway {
    func receivePayment(amount: Double)
    var totalPayments: Double {get}
}

public class PayPal: PaymentGateway {
    private var total = 0.0

    public func receivePayment(amount: Double) {
        self.total += amount
    }

    public var totalPayments: Double {
        print("Total payments received via PayPal: \(self.total)")
        return self.total
    }

    public init() {}
}

public class Stripe: PaymentGateway {
    private var total = 0.0

    public func receivePayment(amount: Double) {
        self.total += amount
    }

    public var totalPayments: Double {
        print("Total payments received via Venmo: \(self.total)")
        return self.total
    }

    public init() {}
}
  • PaymentGatewayに準拠していない外部のコード由来(という設定)のAmazonPaymentsクラス
ThirdPartyPayment.swift
import Foundation

//Third-party class that does not conform to PaymentGateway protocol.
public class AmazonPayments {
    public var payments = 0.0

    public func paid(value: Double, currency: String) {
        self.payments += value
        print("Paid \(currency)\(value) via Amazon Payments")
    }

    public func fulfilledTransactions() -> Double {
        return self.payments
    }

    public init() {}
}
  • AmazonPaymentsクラスだけは、他のクラスと一緒に処理する事ができない
PaymentGateways.playgorund
let paypal = PayPal()
paypal.receivePayment(amount: 100)
paypal.receivePayment(amount: 200)
paypal.receivePayment(amount: 499)

let stripe = Stripe()
stripe.receivePayment(amount: 5.99)
stripe.receivePayment(amount: 25)
stripe.receivePayment(amount: 9.99)

let amazonPayments = AmazonPayments()
amazonPayments.paid(value: 120, currency: "USD")
amazonPayments.paid(value: 74.99, currency: "USD")

var paymentGateways: [PaymentGateway] = [paypal, stripe]//AmazonPaymentsクラスは配列に追加する事ができない

var total = 0.0
paymentGateways.forEach {total += $0.totalPayments}
print(total)

2. アダプターの実装

  • PaymentGatewayプロトコルに準拠したAmazonPaymentsAdapterクラスを作成し、AmazonPaymentsクラスを処理させる
Adapter.swift
import Foundation

public class AmazonPaymentsAdapter: PaymentGateway {
    public func receivePayment(amount: Double) {
        self.amazonPayments.paid(value: amount, currency: "USD")
    }

    public var totalPayments: Double {
        let total = self.amazonPayments.payments
        print("Total payments received via Amazon Payments: \(total)")
        return total
    }

    //AmazonPaymentsクラスを変数として持ち、扱いやすいように処理する。
    //外部からのアクセスを防ぐため、private修飾子をつける。
    private let amazonPayments = AmazonPayments()

    public init() {}
}
  • 使用例
PaymentGateways.playground
let paypal = PayPal()
paypal.receivePayment(amount: 100)
paypal.receivePayment(amount: 200)
paypal.receivePayment(amount: 499)

let stripe = Stripe()
stripe.receivePayment(amount: 5.99)
stripe.receivePayment(amount: 25)
stripe.receivePayment(amount: 9.99)

//アダプターを使用する事で、AmazonPaymentsクラスには直接触らなくて済む
let amazonPaymentsAdapter = AmazonPaymentsAdapter()
amazonPaymentsAdapter.receivePayment(amount: 120)
amazonPaymentsAdapter.receivePayment(amount: 74.99)

var paymentGateways: [PaymentGateway] = [paypal, stripe, amazonPaymentsAdapter]//アダプターを介して、AmazonPaymentsクラスを自作クラスと同じ型として扱えるようになった。

3. extensionを用いたアダプター

Swiftの機能としてextensionというものがある。これは、既存のクラスに自作の変数や関数などを追加できるというSwiftの機能である。

※詳細は
https://www.sejuku.net/blog/33334
https://docs.swift.org/swift-book/LanguageGuide/Extensions.html
などを参照

さらには、extensionを使って、今まで準拠していなかったプロトコルに準拠させるということもできる。

アダプターパターンの実現に当たって、より直感的な実装が可能となる。

本プログラムにおいては、PaymentGatewayプロトコルに準拠していなかったため扱いづらかったAmazonPaymentsクラスを、extensionにより準拠させる事ができる。

AmazonPaymentsAdapterというアダプタークラスを別に作る必要がないため、非常に便利と言える。

ThirdPartyPayment.swift
//extensionによりPaymentGatewayプロトコルに準拠させる。
extension AmazonPayments: PaymentGateway {
    public func receivePayment(amount: Double) {
        self.paid(value: amount, currency: "USD")
    }

    public var totalPayments: Double {
        let total = self.payments
        print("Total payments received via Amazon Payments: \(total)")
        return total
    }
}
PaymentGateways
let paypal = PayPal()
paypal.receivePayment(amount: 100)
paypal.receivePayment(amount: 200)
paypal.receivePayment(amount: 499)

let stripe = Stripe()
stripe.receivePayment(amount: 5.99)
stripe.receivePayment(amount: 25)
stripe.receivePayment(amount: 9.99)

let amazonPayments = AmazonPayments()
amazonPayments.receivePayment(amount: 120)
amazonPayments.receivePayment(amount: 74.99)

//AmazonPayments型を自作クラスと同じ型の配列に入れられるようになった
var paymentGateways: [PaymentGateway] = [paypal, stripe, amazonPayments]

参考文献: https://www.amazon.com/Design-Patterns-Swift-implement-Improve-ebook/dp/B07MDD3FQJ