SwiftのprotocolとextensionをC++のinterface感覚から整理する

Swiftで設計を見ていると、protocolextension がよく出てきます。

C++の抽象クラスやinterface的なものに近いですが、extensionで後から実装を足せるところがSwiftらしいです。

公式では、protocolは特定の役割に必要なメソッドやプロパティのblueprintを定義するものとして説明されています。

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols/

protocolは要求を書く

protocol Drawable {
    func draw()
}

このprotocolに準拠する型は draw() を実装します。

struct Circle: Drawable {
    func draw() {
        print("circle")
    }
}

C++の抽象基底クラスに近いです。

struct Drawable {
    virtual void draw() = 0;
    virtual ~Drawable() = default;
};

ただしSwiftのprotocolは、classだけでなくstructやenumにも使えます。

extensionで実装を足す

Swiftではextensionで型に機能を追加できます。

extension String {
    var isNotEmpty: Bool {
        !isEmpty
    }
}

既存の型にも追加できます。

if "abc".isNotEmpty {
    print("ok")
}

C++なら自由関数にしそうな補助処理が、Swiftではextensionとして書かれることがあります。

protocol extensionでデフォルト実装を書く

protocolに対してextensionを書くと、デフォルト実装を提供できます。

protocol Loggable {
    var name: String { get }
}

extension Loggable {
    func log() {
        print("[log] \(name)")
    }
}

準拠する型は name だけ持てば、log() を使えます。

struct User: Loggable {
    let name: String
}

User(name: "A").log()

この感じは、C++のCRTPやテンプレート関数で共通処理を足す感覚に少し近いです。

継承の代わりにprotocolを使う場面

Swiftでは、継承よりprotocolで能力を表す場面が多いです。

protocol IdentifiableItem {
    var id: String { get }
}

protocol DisplayNameProvider {
    var displayName: String { get }
}

必要な能力を小さく切ると、structにもclassにも使えます。

大きな基底クラスを作るより、Swiftではこの形が読みやすいことが多いです。

まとめ

Swiftのprotocolは、C++のinterfaceに近いですが、structやenumにも使えるのが大きいです。

  • protocolは要求を書く
  • extensionで既存型に機能を足せる
  • protocol extensionでデフォルト実装を書ける
  • 継承より能力の組み合わせとして使う場面が多い

C++の抽象クラスだけを想像すると少し狭いです。Swiftでは「この型が何をできるか」をprotocolで表す、と見ると理解しやすいです。

参考