
(在我的网站上阅读这篇法语文章)
在面向对象编程中,mixin 是一种向类添加一个或多个预定义和自治功能的方法。有些语言直接提供此功能,而其他语言则需要更多的努力和妥协来编码 mixin。在本文中,我将解释 kotlin 中使用委托的 mixin 实现。
mixin 模式的定义并不像 singleton 或 proxy 等其他设计模式那样精确。根据上下文的不同,该术语的含义可能会略有不同。
这种模式也可以接近其他语言(例如 rust)中存在的“traits”,但类似地,术语“trait”不一定意味着相同的事物,具体取决于所使用的语言1.
也就是说,这是来自维基百科的定义:
在面向对象编程中,mixin(或 mix-in)是一个包含其他类使用的方法的类,而无需成为其他类的父类。这些其他类访问 mixin 方法的方式取决于语言。 mixin 有时被描述为“包含”而不是“继承”。
您还可以在有关基于 mixin 的编程主题的各种文章中找到定义(2、3、4)。这些定义还带来了类扩展的概念,而没有经典继承提供的父子关系(或 is-a)。它们进一步与多重继承联系起来,这在 kotlin 中(在 java 中也是不可能的)是不可能的,但它是使用 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 不能包含状态。事实上,虽然 kotlin 中的接口可以定义属性,但它们不能直接初始化它们。因此,每个使用 mixin 的类都必须定义 mixin 操作所需的所有属性。这不符合我们不希望使用 mixin 来强制我们向使用它的类添加属性或方法的约束。
因此,我们需要找到一种解决方案,使 mixin 具有状态,同时保持接口作为同时拥有类型和使用多个 mixin 的能力的唯一方法。
这个解决方案定义 mixin 稍微复杂一些;但是,它对使用它的类没有影响。技巧是将每个 mixin 与一个对象关联起来,以包含 mixin 可能需要的状态。我们将通过将这个对象与 kotlin 的委托功能相关联来使用该对象,以便为每次使用 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...
}
}
我们可以进一步改进实现:counterholder 类是一个实现细节,不需要知道它的名称会很有趣。
为了实现这一点,我们将使用 mixin 接口上的伴随对象和“工厂方法”模式来创建包含 mixin 状态的对象。我们还将使用一些 kotlin 黑魔法,因此我们不需要知道这个方法的名称:
interface counter {
// the functions and properties defined here constitute the mixin's "interface contract." this can be used by the class using the mixin or from outside of it.
fun increment()
fun get(): int
companion object {
private class mixinstateholder : counter {
// the mixin's state can be defined here, and it is private if not also defined in the interface
var count: int = 0
override fun increment() {
count++
}
override fun get(): int = count
}
// using the invoke operator in a companion object makes it appear as if the interface had a constructor. normally i discourage this kind of black magic, but here it seems one of the rare justified cases. if you don't like it, rename this function using a standard name common to all mixins like `init` or `create`.
operator fun invoke(): counter {
return mixinstateholder()
}
}
}
class myclass: counter by counter() {
fun myfunction() {
this.increment()
// the rest of the method...
}
}
mixin 的这种实现并不完美(在我看来,如果没有语言级别的支持,任何一个都不可能是完美的)。特别是,它存在以下缺点:
class myclass: mymixin by mymixin(this) {} // compilation error: `this` is not defined in this context
如果你在 mixin 中使用它,你会引用 holder 类实例。
为了加深对我在本文中提出的模式的理解,这里有一些 mixins 的实际示例。
这个 mixin 允许类“记录”对该类的实例执行的操作。 mixin 提供了另一种方法来检索最新事件。
import java.time.instant
data class timestampedevent(
val timestamp: instant,
val event: string
)
interface auditable {
fun auditevent(event: string)
fun getlatestevents(n: int): list<timestampedevent>
companion object {
private class holder : auditable {
private val events = mutablelistof<timestampedevent>()
override fun auditevent(event: string) {
events.add(timestampedevent(instant.now(), event))
}
override fun getlatestevents(n: int): list<timestampedevent> {
return events.sortedbydescending(timestampedevent::timestamp).takelast(n)
}
}
operator fun invoke(): auditable = holder()
}
}
class bankaccount: auditable by auditable() {
private var balance = 0
fun deposit(amount: int) {
auditevent("deposit $amount")
balance += amount
}
fun withdraw(amount: int) {
auditevent("withdraw $amount")
balance -= amount
}
fun getbalance() = balance
}
fun main() {
val myaccount = bankaccount()
// this function will call deposit and withdraw many times but we don't know exactly when and how
givetocomplexsystem(myaccount)
// we can query the balance of the account
myaccount.getbalance()
// thanks to the mixin, we can also know the operations that have been performed on the account.
myaccount.getlatestevents(10)
}
observable 设计模式可以使用 mixin 轻松实现。这样,可观察类不再需要定义订阅和通知逻辑,也不需要自己维护观察者列表。
interface observable<t> {
fun subscribe(observer: (t) -> unit)
fun notifyobservers(event: t)
companion object {
private class holder<t> : observable<t> {
private val observers = mutablelistof<(t) -> unit>()
override fun subscribe(observer: (t) -> unit) {
observers.add(observer)
}
override fun notifyobservers(event: t) {
observers.foreach { it(event) }
}
}
operator fun <t> invoke(): observable<t> = holder()
}
}
sealed interface catalogevent
class priceupdated(val product: string, val price: int): catalogevent
class catalog(): observable<catalogevent> by observable() {
val products = mutablemapof<string, int>()
fun updateprice(product: string, price: int) {
products[product] = price
notifyobservers(priceupdated(product, price))
}
}
fun main() {
val catalog = catalog()
catalog.subscribe { println(it) }
catalog.updateprice("lamp", 10)
}
但是,在这种特定情况下有一个缺点:notifyobservers 方法可以从 catalog 类外部访问,即使我们可能更愿意将其保持为私有。但所有 mixin 方法都必须是公共的,才能从使用 mixin 的类中使用(因为我们不使用继承而是组合,即使 kotlin 简化的语法使其看起来像继承)。
如果您的项目管理持久性业务数据和/或您至少部分实践 ddd(领域驱动设计),那么您的应用程序可能包含实体。实体是具有身份的类,通常实现为数字 id 或 uuid。这个特性非常适合 mixin 的使用,这里是一个例子。
interface Entity {
val id: UUID
// Overriding equals and hashCode in a mixin may not always be a good idea, but it seems interesting for the example.
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
}
class IdentityHolder(
override val id: UUID
): Entity {
// Two entities are equal if their ids are equal.
override fun equals(other: Any?): Boolean {
if(other is Entity) {
return this.id == other.id
}
return false
}
override fun hashCode(): Int {
return id.hashCode()
}
}
class Customer(
id: UUID,
val firstName: String,
val lastName: String,
) : Entity by IdentityHolder(id)
val id = UUID.randomUUID()
val c1 = Customer(id, "John", "Smith")
val c2 = Customer(id, "John", "Doe")
c1 == c2 // true
这个例子有点不同:我们看到没有什么可以阻止我们以不同的方式命名 holder 类,也没有什么可以阻止我们在实例化期间传递参数。
mixin 技术允许通过添加横向和可重用的行为来丰富类,而无需修改这些类来适应这些功能。尽管存在一些限制,mixin 有助于促进代码重用并隔离应用程序中多个类所共有的某些功能。
mixin 是 kotlin 开发者工具包中一个有趣的工具,我鼓励您在自己的代码中探索此方法,同时了解限制和替代方案。
有趣的事实:kotlin 有一个 trait 关键字,但它已被弃用并已被 interface 取代(请参阅 https://blog.jetbrains.com/kotlin/2015/05/kotlin-m12-is-out/#traits -现在是接口)↩
基于 mixin 的继承↩
类和混入↩
具有风格的面向对象编程 ↩
以上就是使用委托在 Kotlin 中实现 Mixins(或 Traits)的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号