by 演算子を使用したインターフェースの委譲は、クラスがインターフェースのすべての呼び出しを特定の委譲オブジェクトにリダイレクトできるようにします。これにより、コードの重複が減少し、コンポジションパターンを実現します。
例:
interface Logger { fun log(message: String) } class ConsoleLogger: Logger { override fun log(message: String) = println("LOG: $message") } class Service(logger: Logger): Logger by logger { fun doWork() { log("Service is working") } } val service = Service(ConsoleLogger()) service.doWork() // LOG: Service is working
プロパティの委譲との違い:
val/var x by ...) は特定のプロパティに適用され、委譲インターフェース(例えば ReadWriteProperty)の実装が必要です。利点:
欠点:
インターフェースの委譲 (
by) はフィールドを介してオブジェクトを渡すインターフェースの実装と何が違いますか?
答え:
委譲(by を通じて)は、委譲オブジェクトを介してインターフェースのすべてのメソッドを自動的に実装します。オブジェクトの委譲をフィールドとして保持し、そのメソッドを手動で呼び出す場合、インターフェースの各メソッドを手動で書く必要があり、重複とエラーの原因になります。さらに、by を介した委譲は可読性が高く、ブイラーコードが少なくなります。
// 委譲なし class Service2(private val logger: Logger): Logger { override fun log(message: String) = logger.log(message) }
物語
プロジェクトで Logger インターフェースのデコレータパターンを手動で実装しようとしたところ、後で Logger に追加された追加メソッドを実装するのを忘れました。プロジェクトはコンパイルされましたが、新しい機能は動作しませんでした。実装が "空のもの" だったためです。by を通じたインターフェースの委譲により、このエラーを回避できたでしょう:すべての新しいメソッドは自動的に委譲されます。
物語
by を介したインターフェースの委譲中に、開発者はインターフェースのメソッドの1つをオーバーライドしましたが、他のメソッドはすべて委譲経由で行われることを忘れていました。その結果、一部の機能が "標準でない" 方法で動作しました — エラーはビジネスメソッドのロジックの中で長い間検出されませんでした。
物語
同じメソッドを持つ複数のインターフェースの委譲を by を介して実装しようとしたところ、競合が発生し、コンパイラは曖昧さのエラーを返しました。重複するメソッドを明示的にオーバーライドする必要がありました。そうしないと、プロジェクトはビルドされませんでした。