Kotlinでは、振る舞いの委譲はクラスのシグネチャに直接書かれたbyというキーワードを使用して言語的に実装されています。これにより、インターフェース(または複数のインターフェース)によるメソッド呼び出しを別の実装オブジェクトに自動的に転送し、ボイラープレートを削減し、コンポジションを容易にします。
インターフェースの委譲の出現は、多重継承の制約や欠点を取り除く試みです。これは「継承よりもコンポジション」というアイデアであり、クラス階層に頼ることなく振る舞いを委譲します。これは、コンポジションがより人気のある言語(例えばGoやScala)から借用されたものです。
Javaや他の言語では、インターフェースを作成して各メソッドを手動で実装し、そのロジックを別のフィールドに渡さなければならないことが多く(オブジェクトアダプタパターン)、メソッドの数が増えるにつれてすぐに古くなります。
Kotlinは、byを使用してインターフェースを宣言的に委譲することを可能にします:
interface Logger { fun log(msg: String) } class ConsoleLogger: Logger { override fun log(msg: String) = println(msg) } class Service(logger: Logger): Logger by logger { fun doWork() { log("Work started") // ... } } val service = Service(ConsoleLogger()) service.doWork()
Loggerのすべてのメソッドは、提供されたloggerオブジェクトを通じて実装され、Serviceクラスでは明示的にメソッドをオーバーライドしたりプロキシされたりする必要がありません。主な特徴:
Serviceクラスに同じシグネチャの自分自身のメソッドを追加するとどうなりますか?
独自の実装が「委譲」を「オーバーライド」します。つまり、クラス内で明示的に定義されたメソッドが優先されます:
class Service(logger: Logger): Logger by logger { override fun log(msg: String) = println("PREFIX: $msg") }
1つのクラスが異なるオブジェクトに複数のインターフェースを委譲できますか?
はい、クラスは複数のインターフェースを異なるオブジェクトに実装および委譲できますが、各インターフェースは1つのオブジェクトに委譲されます:
class Service( logger: Logger, tracker: Tracker ): Logger by logger, Tracker by tracker
インターフェースの委譲とは、byを介したプロパティの委譲とは何が違いますか?
ReadOnlyProperty、ReadWriteProperty)に対するget/setの動作を委譲します。クラスが手動でインターフェースを実装し、各メソッドがデリゲートを呼び出し、新しいメソッドを追加する際にプロキシを更新し忘れるためにエラーが発生します。
利点:
欠点:
言語の委譲が使用され、非標準メソッドだけがクラス内で実装され、新しい機能が大きな変更なしで追加されます。
利点:
欠点: