ホームページ >Java >&#&チュートリアル >委任を使用した Kotlin でのミックスイン (またはトレイト) の実装

委任を使用した Kotlin でのミックスイン (またはトレイト) の実装

Mary-Kate Olsen
Mary-Kate Olsenオリジナル
2024-10-18 20:11:031069ブラウズ

Implementing Mixins (or Traits) in Kotlin Using Delegation

(私のウェブサイトでこの記事をフランス語で読んでください)

オブジェクト指向プログラミングでは、ミックスインは、1 つ以上の事前定義された自律機能をクラスに追加する方法です。一部の言語ではこの機能が直接提供されますが、他の言語では Mixin をコーディングするためにより多くの労力と妥協が必要になります。この記事では、委任を使用した Kotlin での Mixins の実装について説明します。

  • 客観的
    • 「Mixins」パターンの定義
    • 特性と制約
  • 実装
    • 構成による素朴なアプローチ
    • 継承の使用
    • ミクシン国家を封じ込める代表団
    • 最終実装
  • 制限事項
    • 監査可能
    • 観測可能
    • エンティティ/アイデンティティ
  • 結論

客観的

「ミックスイン」パターンの定義

ミックスイン パターンは、シングルトンやプロキシなどの他のデザイン パターンほど正確に定義されていません。文脈によっては、この用語の意味が若干異なる場合があります。

このパターンは、他の言語 (Rust など) に存在する「トレイト」に近い場合もありますが、同様に、「トレイト」という用語は、使用される言語によっては必ずしも同じ意味を持ちません1.

とはいえ、ウィキペディアからの定義は次のとおりです。

オブジェクト指向プログラミングでは、ミックスイン (またはミックスイン) は、他のクラスの親クラスである必要がなく、他のクラスによって使用されるメソッドを含むクラスです。これらの他のクラスがミックスインのメソッドにアクセスする方法は言語によって異なります。ミックスインは、「継承」ではなく「組み込まれている」と表現されることがあります。

mixin ベースのプログラミングに関するさまざまな記事でも定義を見つけることができます (234)。これらの定義は、古典的な継承によって提供される親子関係 (または is-a) を持たないクラス拡張の概念ももたらします。さらに、多重継承とリンクします。これは Kotlin (Java でも) では不可能ですが、ミックスインを使用する利点の 1 つとして提示されています。

特性と制約

これらの定義に厳密に一致するパターンの実装は、次の制約を満たす必要があります。

  • クラスに複数のミックスインを追加できます。私たちはオブジェクト指向のコンテキストにいますが、この制約が満たされない場合、このパターンは継承などの他の設計可能性と比べてほとんど関心がありません。
  • ミックスイン機能はクラス外から使用できます。同様に、この制約を無視すると、パターンは単純な構成では達成できなかったものを何ももたらしません。
  • クラスにミックスインを追加しても、クラス定義に属性やメソッドを追加する必要はありません。この制約がなければ、ミックスインはもはや「ブラック ボックス」機能とは見なされません。 mixin のインターフェイス コントラクトに依存してクラスに追加するだけでなく、その機能を (ドキュメントなどを介して) 理解する必要があります。クラスや関数を使うのと同じようにミックスインも使えるようにしたいです
  • ミックスインは状態を持つことができます。一部のミックスインは、その機能のためにデータを保存する必要がある場合があります。
  • ミックスインはタイプとして使用できます。たとえば、特定のミックスインを使用する限り、任意のオブジェクトをパラメーターとして受け取る関数を作成できます。

実装

構成による素朴なアプローチ

クラスに機能を追加する最も簡単な方法は、別のクラスを属性として使用することです。この属性のメソッドを呼び出すことで、ミックスインの機能にアクセスできるようになります。

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 のインターフェイスはプロパティを定義できますが、プロパティを直接初期化することはできません。したがって、ミックスインを使用するすべてのクラスは、ミックスインの操作に必要なすべてのプロパティを定義する必要があります。これは、ミックスインを使用してクラスにプロパティやメソッドを追加することを強制したくないという制約を尊重しません。

したがって、タイプと複数のミックスインを使用する機能の両方を持つための唯一の方法としてインターフェイスを維持しながら、ミックスインが状態を持つためのソリューションを見つける必要があります。

ミックスイン国家を封じ込める代表団

このソリューションは、ミックスインを定義するために少し複雑です。ただし、それを使用するクラスには影響しません。秘訣は、各ミックスインをオブジェクトに関連付けて、ミックスインが必要とする可能性のある状態を含めることです。このオブジェクトを 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")
    }
}

制限事項

このミックスインの実装は完璧ではありません (私の意見では、言語レベルでサポートされなければ完璧なものはあり得ません)。特に、次のような欠点があります:

  • すべてのミックスイン メソッドはパブリックである必要があります。一部のミックスインには、そのミックスインを使用するクラスによって使用されることを目的としたメソッドが含まれており、その他のミックスインは外部から呼び出された方が意味があるものです。 mixin はインターフェイス上でメソッドを定義するため、コンパイラーにこれらの制約を強制的に検証させることはできません。その場合は、ドキュメントまたは静的コード分析ツールに頼る必要があります。
  • ミックスイン メソッドは、ミックスインを使用してクラスのインスタンスにアクセスできません。委任宣言時点ではインスタンスは初期化されていないため、ミックスインに渡すことができません。
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 開発者ツールキットの興味深いツールです。制約と代替手段を意識しながら、独自のコードでこのメソッドを検討することをお勧めします。


  1. 面白い事実: Kotlin には trait キーワードがありますが、これは非推奨であり、インターフェイスに置き換えられています (https://blog.jetbrains.com/kotlin/2015/05/kotlin-m12-is-out/#traits を参照) -are-now-interfaces) ↩

  2. Mixin ベースの継承 ↩

  3. クラスとミックスイン ↩

  4. フレーバー を使用したオブジェクト指向プログラミング

以上が委任を使用した Kotlin でのミックスイン (またはトレイト) の実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。