ホームページ >Java >&#&チュートリアル >委任を使用した Kotlin でのミックスイン (またはトレイト) の実装
(私のウェブサイトでこの記事をフランス語で読んでください)
オブジェクト指向プログラミングでは、ミックスインは、1 つ以上の事前定義された自律機能をクラスに追加する方法です。一部の言語ではこの機能が直接提供されますが、他の言語では Mixin をコーディングするためにより多くの労力と妥協が必要になります。この記事では、委任を使用した Kotlin での Mixins の実装について説明します。
ミックスイン パターンは、シングルトンやプロキシなどの他のデザイン パターンほど正確に定義されていません。文脈によっては、この用語の意味が若干異なる場合があります。
このパターンは、他の言語 (Rust など) に存在する「トレイト」に近い場合もありますが、同様に、「トレイト」という用語は、使用される言語によっては必ずしも同じ意味を持ちません1.
とはいえ、ウィキペディアからの定義は次のとおりです。
オブジェクト指向プログラミングでは、ミックスイン (またはミックスイン) は、他のクラスの親クラスである必要がなく、他のクラスによって使用されるメソッドを含むクラスです。これらの他のクラスがミックスインのメソッドにアクセスする方法は言語によって異なります。ミックスインは、「継承」ではなく「組み込まれている」と表現されることがあります。
mixin ベースのプログラミングに関するさまざまな記事でも定義を見つけることができます (2、3、4)。これらの定義は、古典的な継承によって提供される親子関係 (または is-a) を持たないクラス拡張の概念ももたらします。さらに、多重継承とリンクします。これは Kotlin (Java でも) では不可能ですが、ミックスインを使用する利点の 1 つとして提示されています。
これらの定義に厳密に一致するパターンの実装は、次の制約を満たす必要があります。
クラスに機能を追加する最も簡単な方法は、別のクラスを属性として使用することです。この属性のメソッドを呼び出すことで、ミックスインの機能にアクセスできるようになります。
class MyClass { private val mixin = Counter() fun myFunction() { mixin.increment() // ... } }
このメソッドは、Kotlin の型システムに情報を提供しません。たとえば、Counter を使用してオブジェクトのリストを取得することは不可能です。 Counter 型のオブジェクトをパラメータとして取得しても、この型はミックスインのみを表し、オブジェクトはアプリケーションの残りの部分にはおそらく役に立たないため、意味がありません。
この実装のもう 1 つの問題は、このクラスを変更するかミックスインを公開しない限り、クラスの外部からミックスインの機能にアクセスできないことです。
ミックスインがアプリケーションで使用できる型を定義するには、抽象クラスから継承するか、インターフェイスを実装する必要があります。
抽象クラスを使用してミックスインを定義することは問題外です。1 つのクラスで複数のミックスインを使用することはできません (Kotlin では複数のクラスから継承することは不可能です)。
このようにして、インターフェースを備えたミックスインが作成されます。
interface Counter { var count: Int fun increment() { println("Mixin does its job") } fun get(): Int = count } class MyClass: Counter { override var count: Int = 0 // We are forced to add the mixin's state to the class using it fun hello() { println("Class does something") } }
このアプローチは、いくつかの理由により、前のアプローチよりも満足のいくものです。
ただし、この実装には依然として重大な制限があり、ミックスインには状態を含めることができません。実際、Kotlin のインターフェイスはプロパティを定義できますが、プロパティを直接初期化することはできません。したがって、ミックスインを使用するすべてのクラスは、ミックスインの操作に必要なすべてのプロパティを定義する必要があります。これは、ミックスインを使用してクラスにプロパティやメソッドを追加することを強制したくないという制約を尊重しません。
したがって、タイプと複数のミックスインを使用する機能の両方を持つための唯一の方法としてインターフェイスを維持しながら、ミックスインが状態を持つためのソリューションを見つける必要があります。
このソリューションは、ミックスインを定義するために少し複雑です。ただし、それを使用するクラスには影響しません。秘訣は、各ミックスインをオブジェクトに関連付けて、ミックスインが必要とする可能性のある状態を含めることです。このオブジェクトを Kotlin の委任機能に関連付けて使用し、ミックスインを使用するたびにこのオブジェクトを作成します。
すべての制約を満たしながらも、基本的なソリューションを次に示します。
class MyClass { private val mixin = Counter() fun myFunction() { mixin.increment() // ... } }
実装をさらに改善できます。CounterHolder クラスは実装の詳細であり、その名前を知る必要がないのは興味深いことです。
これを実現するには、ミックスイン インターフェイスのコンパニオン オブジェクトと「ファクトリー メソッド」パターンを使用して、ミックスインの状態を含むオブジェクトを作成します。また、Kotlin の黒魔術も少し使用するので、このメソッドの名前を知る必要はありません:
interface Counter { var count: Int fun increment() { println("Mixin does its job") } fun get(): Int = count } class MyClass: Counter { override var count: Int = 0 // We are forced to add the mixin's state to the class using it fun hello() { println("Class does something") } }
このミックスインの実装は完璧ではありません (私の意見では、言語レベルでサポートされなければ完璧なものはあり得ません)。特に、次のような欠点があります:
interface Counter { fun increment() fun get(): Int } class CounterHolder: Counter { var count: Int = 0 override fun increment() { count++ } override fun get(): Int = count } class MyClass: Counter by CounterHolder() { fun hello() { increment() // The rest of the method... } }
これをミックスイン内で使用する場合は、Holder クラスのインスタンスを参照します。
この記事で提案するパターンの理解を深めるために、ミックスインの現実的な例をいくつか示します。
このミックスインにより、クラスは、そのクラスのインスタンスで実行されたアクションを「記録」できます。ミックスインは、最新のイベントを取得する別の方法を提供します。
class MyClass { private val mixin = Counter() fun myFunction() { mixin.increment() // ... } }
デザインパターン Observable は、ミックスインを使用して簡単に実装できます。このようにして、監視可能なクラスはサブスクリプションと通知のロジックを定義したり、オブザーバー自体のリストを管理したりする必要がなくなります。
interface Counter { var count: Int fun increment() { println("Mixin does its job") } fun get(): Int = count } class MyClass: Counter { override var count: Int = 0 // We are forced to add the mixin's state to the class using it fun hello() { println("Class does something") } }
ただし、この特定のケースには欠点があります。notifyObservers メソッドは、おそらく非公開にしておいた方がよいにもかかわらず、Catalog クラスの外部からアクセスできます。ただし、ミックスインを使用してクラスから使用するには、すべてのミックスイン メソッドがパブリックである必要があります (Kotlin によって簡略化された構文によって継承のように見えても、継承ではなく合成を使用しているためです)。
プロジェクトが永続的なビジネス データを管理している場合、または少なくとも部分的に DDD (ドメイン駆動設計) を実践している場合、アプリケーションにはエンティティが含まれている可能性があります。エンティティは ID を持つクラスであり、多くの場合、数値 ID または UUID として実装されます。この特性はミックスインの使用によく当てはまります。ここに例を示します。
interface Counter { fun increment() fun get(): Int } class CounterHolder: Counter { var count: Int = 0 override fun increment() { count++ } override fun get(): Int = count } class MyClass: Counter by CounterHolder() { fun hello() { increment() // The rest of the method... } }
この例は少し異なります。Holder クラスに別の名前を付けることを妨げるものは何もなく、インスタンス化中にパラメーターを渡すことを妨げるものは何もないことがわかります。
ミックスイン技術を使用すると、これらの機能に対応するためにクラスを変更することなく、横断的で再利用可能な動作を追加することでクラスを強化できます。いくつかの制限はありますが、ミックスインはコードの再利用を容易にし、アプリケーション内の複数のクラスに共通する特定の機能を分離するのに役立ちます。
ミックスインは Kotlin 開発者ツールキットの興味深いツールです。制約と代替手段を意識しながら、独自のコードでこのメソッドを検討することをお勧めします。
面白い事実: Kotlin には trait キーワードがありますが、これは非推奨であり、インターフェイスに置き換えられています (https://blog.jetbrains.com/kotlin/2015/05/kotlin-m12-is-out/#traits を参照) -are-now-interfaces) ↩
Mixin ベースの継承 ↩
クラスとミックスイン ↩
フレーバー を使用したオブジェクト指向プログラミング
↩以上が委任を使用した Kotlin でのミックスイン (またはトレイト) の実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。