post Image
Swiftで静的DI(Mixin-Injection)

元ネタ:SwiftでもMinimal Cake Pattern

ほぼ上記投稿で解説されているとおりなのですが、こちらの投稿ではMixinモジュールの生成方法にclassを利用しています。
Mixinモジュールをclassで定義してしまうと、Mixinモジュールを注入したいオブジェクト内部でMixinモジュールのインスタンス化を行う必要があり、Mixinモジュールが注入先に依存してしまっているのが気になりました。

こちらの投稿ではMixinモジュールにProtocolを利用する方法で注入先がMixinモジュールの初期化に関心を持たないようにしてみようと思います。

Loggerインターフェース

Loggerインターフェースを注入するパターンとして考えてみます。

Loggerにはそれぞれ、SystemLoggerとTextLoggerという具象クラスが存在するものとします。

protocol Logger {
    func write(_ message: String)
}

struct SystemLogger: Logger {
    func write(_ message: String) {
        print("System: " + message)
    }
}

struct TextLogger: Logger {
    func write(_ message: String) {
        // 本当はローカルのテキストファイルに書き込む処理にするべきだが、コード量が増えるため標準出力への書き込みとしている。
        print("Text: " + message)
    }
}

モジュールを提供するためのインターフェース

モジュールを提供するためのインターフェースを定義します。

今回注入したいのはLoggerですので、UsesLoggerと命名します。

MixinモジュールをProtocolで定義する都合、プロパティはGetOnlyとします。

protocol UsesLogger {
    var logger: Logger { get }
}

Mixinモジュール

それぞれ、SystemLoggerとTextLoggerを提供する方法を定義したモジュールを定義します。

protocol MixinSystemLogger: UsesLogger {}
extension MixinSystemLogger {
    var logger: Logger { return SystemLogger() }
}

protocol MixinTextLogger: UsesLogger {}
extension MixinTextLogger {
    var logger: Logger { return TextLogger() }
}

依存を注入するクライアント

クライアントオブジェクトに対して、注入したいMixinモジュールを定義すれば完了です。
Protocolを適宜追加するだけで複数の依存を注入することも出来ます。

処理を差し替えたいときは、UsesLoggerに適合したMixinモジュールを差し替えてあげれば良く、間違えて同じUsesLoggerに適合したMixinモジュールを指定した場合はコンパイルエラーとなるので安全でもあります。

struct SystemLoggerClient: MixinSystemLogger {
    func test() {
        logger.write("test")
    }
}

struct TextLoggerClient: MixinTextLogger {
    func test() {
        logger.write("test")
    }
}

let systemLoggerClient = SystemLoggerClient()
systemLoggerClient.test() // "System: test"

let textLoggerClient = TextLoggerClient()
textLoggerClient.test() // "Text: test"

ちなみに、Mixinモジュールは毎回インスタンスの生成が行われれしまうのでパフォーマンスが気になるときはクライアント側に依存オブジェクトをキャッシュするプロパティを用意しておくと良いです(この場合、オブジェクトはclassで定義しないとならない)。

final class SystemLoggerClient: MixinSystemLogger {
    private lazy var _logger: Logger = self.logger

    func test() {
        _logger.write("test")
    }
}

テスト

Swiftではextensionにwhere制約を設けることで多態性を表現することが出来ます
この機能を利用して、MixinモジュールがMockオブジェクトになるようにします。

// Mockとして差し替えたいLoggerオブジェクト
private struct MockLogger: Logger {
    func write(_ message: String) {
        print("Mock: " + message)
    }
}

// Mockに差し替えるためのマーカープロトコルを定義
private protocol Mock {}

// Mixin先オブジェクトがMockの抽象ならMock用Loggerを返すようにする
private extension MixinSystemLogger where Self: Mock {
    var logger: Logger { return MockLogger() }
}

// テストしたいクライアントにMockをのプロトコルを適用することでMockオブジェクトに差し替える事ができる
private extension SystemLoggerClient: Mock {}

let mockClient = SystemLoggerClient()
mockClient.test() // "Mock: test"

ということで、テストまで含めてSwiftならDIを静的に解決出来ました。

ソースコードはGistにおいてあります

実際に何らかのプロジェクトでこの方法を試したわけではないので有用性についてはそれほど考慮できてはいないですし、問題点についても洗い出せていませんのでこれを読んで皆様色々と考えてみてくれると嬉しい限りでございます。


参考ソース


『 Swift 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。