首頁  >  文章  >  Java  >  使用委託在 Kotlin 中實現 Mixins(或 Traits)

使用委託在 Kotlin 中實現 Mixins(或 Traits)

Mary-Kate Olsen
Mary-Kate Olsen原創
2024-10-18 20:11:03937瀏覽

Implementing Mixins (or Traits) in Kotlin Using Delegation

(在我的網站上閱讀這篇法文文章)

在物件導向程式設計中,Mixin 是一種在類別中新增一個或多個預先定義和自主功能的方法。有些語言直接提供此功能,而其他語言則需要更多的努力和妥協來編碼 Mixin。在本文中,我將解釋 Kotlin 中使用委託的 Mixin 實作。

  • 客觀的
    • 「Mixins」模式的定義
    • 特徵與限制
  • 執行
    • 簡單的組合方法
    • 繼承的使用
    • 控制 Mixin 狀態的委託
    • 最終實施
  • 限制
  • 範例
    • 可審核
    • 可觀察
    • 實體/身分
  • 結論

客觀的

“Mixins”模式的定義

mixin 模式的定義並不像其他設計模式如 Singleton 或 Proxy 那樣精確。根據上下文的不同,該術語的含義可能會略有不同。

這種模式也可以接近其他語言(例如Rust)中存在的“Traits”,但類似地,術語“Trait”不一定意味著相同的事物,這取決於所使用的語言1.

也就是說,這是來自維基百科的定義:

在物件導向程式設計中,mixin(或 mix-in)是一個包含其他類別使用的方法的類,而無需成為其他類別的父類。這些其他類別存取 mixin 方法的方式取決於語言。 Mixin 有時被描述為「包含」而不是「繼承」。

您也可以在各種基於 mixin 的程式設計主題的文章中找到定義(234)。這些定義也帶來了類別擴展的概念,而沒有經典繼承提供的父子關係(或 is-a)。它們進一步與多重繼承聯繫起來,這在 Kotlin(或 Java)中是不可能的,但它是使用 mixins 的好處之一。

特點和限制

與這些定義緊密匹配的模式的實現必須滿足以下約束:

  • 我們可以為一個類別增加多個 mixin。我們處於物件導向的環境中,如果不滿足這個約束,那麼與繼承等其他設計可能性相比,該模式就沒有什麼吸引力。
  • Mixin 功能可以在類別外部使用。同樣,如果我們忽略這個約束,該模式將不會帶來任何我們透過簡單組合無法實現的目標。
  • 為類別新增 mixin 並不會強制我們在類別定義中新增屬性和方法。如果沒有這個限制,mixin 就不能再被視為「黑盒子」功能。我們不僅可以依靠 mixin 的介面契約將其添加到類別中,而且還必須了解其功能(例如,透過文件)。我希望能夠像使用類別或函數一樣使用 mixin。
  • mixin 可以有一個狀態。有些 mixin 可能需要為其功能儲存資料。
  • Mixin 可以用作類型。例如,我可以有一個函數,它可以將任何物件作為參數,只要它使用給定的 mixin。

執行

簡單的組合方法

為類別新增功能的最簡單方法是使用另一個類別作為屬性。然後可以透過呼叫此屬性的方法來存取 mixin 的功能。

class MyClass {
    private val mixin = Counter()

    fun myFunction() {
        mixin.increment()

        // ...
    }
}

此方法不提供 Kotlin 的型別系統任何資訊。例如,不可能使用 Counter 來取得物件清單。將 Counter 類型的物件作為參數沒有意義,因為這種類型僅代表 mixin,因此該物件可能對應用程式的其餘部分無用。

此實作的另一個問題是,如果不修改此類或將 mixin 公開,則無法從類別外部存取 mixin 的功能。

繼承的使用

為了讓 mixin 也定義可在應用程式中使用的類型,我們需要從抽象類別繼承或實作介面。

使用抽象類別來定義 mixin 是不可能的,因為它不允許我們在單一類別上使用多個 mixin(在 Kotlin 中不可能從多個類別繼承)。

因此將建立具有介面的 mixin。

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 的類別不需要實作 mixin 的行為
  • 一個類別可以使用多個 mixin,因為 Kotlin 允許一個類別實作多個介面。
  • 每個 mixin 都會建立一個類型,可用來根據其類別包含的 mixin 來操作物件。

但是,此實作仍然存在一個重大限制:mixin 不能包含狀態。事實上,雖然 Kotlin 中的介面可以定義屬性,但它們不能直接初始化它們。因此,每個使用 mixin 的類別都必須定義 mixin 操作所需的所有屬性。這不符合我們不希望使用 mixin 來強制我們向使用它的類別添加屬性或方法的約束。

因此,我們需要找到一種解決方案,使 mixin 具有狀態,同時保持介面作為同時具有類型和使用多個 mixin 的能力的唯一方法。

委託來遏制 Mixin 的狀態

這個解決方案定義 mixin 稍微複雜一些;但是,它對使用它的類別沒有影響。技巧是將每個 mixin 與一個物件關聯起來,以包含 mixin 可能需要的狀態。我們將透過將這個物件與 Kotlin 的委託功能相關聯來使用該對象,以便為每次使用 mixin 建立該物件。

這是滿足所有限制的基本解決方案:

class MyClass {
    private val mixin = Counter()

    fun myFunction() {
        mixin.increment()

        // ...
    }
}

最終實施

我們可以進一步改進實作:CounterHolder 類別是實作細節,不需要知道它的名稱會很有趣。

為了實現這一點,我們將使用 mixin 介面上的伴隨物件和「工廠方法」模式來建立包含 mixin 狀態的物件。我們也會使用一些 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 的這種實現並不完美(在我看來,如果沒有語言等級的支持,任何一個都不可能是完美的)。特別是,它有以下缺點:

  • 所有 mixin 方法都必須是公共的。一些 mixin 包含旨在由使用 mixin 的類別使用的方法,以及其他如果從外部呼叫則更有意義的方法。由於 mixin 在介面上定義了其方法,因此不可能強制編譯器驗證這些約束。然後我們必須依賴文件或靜態程式碼分析工具。
  • Mixin 方法無法使用 mixin 存取類別的實例。在委託聲明時,實例尚未初始化,我們無法將其傳遞給 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...
    }
}

如果你在 mixin 中使用它,你會引用 Holder 類別實例。

範例

為了加深對我在本文中提出的模式的理解,這裡有一些 mixins 的實際範例。

可審計

這個 mixin 允許類別「記錄」對該類別的實例執行的操作。 mixin 提供了另一種方法來檢索最新事件。

class MyClass {
    private val mixin = Counter()

    fun myFunction() {
        mixin.increment()

        // ...
    }
}

可觀察的

Observable 設計模式可以使用 mixin 輕鬆實現。這樣,可觀察類別不再需要定義訂閱和通知邏輯,也不需要自行維護觀察者清單。

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 類別外部訪問,即使我們可能更願意將其保持為私有。但所有 mixin 方法都必須是公共的,才能從使用 mixin 的類別中使用(因為我們不使用繼承而是組合,即使 Kotlin 簡化的語法使其看起來像繼承)。

實體/身份

如果您的專案管理持久性業務資料和/或您至少部分實踐 DDD(領域驅動設計),那麼您的應用程式可能包含實體。實體是具有身分的類,通常實作為數位 ID 或 UUID。這個特性非常適合 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 類,也沒有什麼可以阻止我們在實例化期間傳遞參數。

結論

mixin 技術允許透過添加橫向和可重複使用的行為來豐富類別,而無需修改這些類別來適應這些功能。儘管存在一些限制,mixins 有助於促進程式碼重複使用並隔離應用程式中多個類別所共有的某些功能。

Mixin 是 Kotlin 開發者工具包中一個有趣的工具,我鼓勵您在自己的程式碼中探索此方法,同時了解限制和替代方案。


  1. 有趣的事實:Kotlin 有一個Trait 關鍵字,但它已被棄用並已被Interface 取代(請參閱https://blog.jetbrains.com/kotlin/2015/05/kotlin-m12-is- out/#traits -現在是介面)↩

  2. 基於 Mixin 的繼承↩

  3. 類和混入↩

  4. 具有風格的物件導向程式設計 ↩

以上是使用委託在 Kotlin 中實現 Mixins(或 Traits)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn