(在我的網站上閱讀這篇法文文章)
在物件導向程式設計中,Mixin 是一種在類別中新增一個或多個預先定義和自主功能的方法。有些語言直接提供此功能,而其他語言則需要更多的努力和妥協來編碼 Mixin。在本文中,我將解釋 Kotlin 中使用委託的 Mixin 實作。
mixin 模式的定義並不像其他設計模式如 Singleton 或 Proxy 那樣精確。根據上下文的不同,該術語的含義可能會略有不同。
這種模式也可以接近其他語言(例如Rust)中存在的“Traits”,但類似地,術語“Trait”不一定意味著相同的事物,這取決於所使用的語言1.
也就是說,這是來自維基百科的定義:
在物件導向程式設計中,mixin(或 mix-in)是一個包含其他類別使用的方法的類,而無需成為其他類別的父類。這些其他類別存取 mixin 方法的方式取決於語言。 Mixin 有時被描述為「包含」而不是「繼承」。
您也可以在各種基於 mixin 的程式設計主題的文章中找到定義(2、3、4)。這些定義也帶來了類別擴展的概念,而沒有經典繼承提供的父子關係(或 is-a)。它們進一步與多重繼承聯繫起來,這在 Kotlin(或 Java)中是不可能的,但它是使用 mixins 的好處之一。
與這些定義緊密匹配的模式的實現必須滿足以下約束:
為類別新增功能的最簡單方法是使用另一個類別作為屬性。然後可以透過呼叫此屬性的方法來存取 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 不能包含狀態。事實上,雖然 Kotlin 中的介面可以定義屬性,但它們不能直接初始化它們。因此,每個使用 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 的這種實現並不完美(在我看來,如果沒有語言等級的支持,任何一個都不可能是完美的)。特別是,它有以下缺點:
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 開發者工具包中一個有趣的工具,我鼓勵您在自己的程式碼中探索此方法,同時了解限制和替代方案。
有趣的事實:Kotlin 有一個Trait 關鍵字,但它已被棄用並已被Interface 取代(請參閱https://blog.jetbrains.com/kotlin/2015/05/kotlin-m12-is- out/#traits -現在是介面)↩
基於 Mixin 的繼承↩
類和混入↩
具有風格的物件導向程式設計 ↩
以上是使用委託在 Kotlin 中實現 Mixins(或 Traits)的詳細內容。更多資訊請關注PHP中文網其他相關文章!