0

0

使用委托在 Kotlin 中实现 Mixins(或 Traits)

聖光之護

聖光之護

发布时间:2024-10-18 20:24:02

|

603人浏览过

|

来源于dev.to

转载

使用委托在 kotlin 中实现 mixins(或 traits)

(在我的网站上阅读这篇法语文章)

在面向对象编程中,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 中也是不可能的)是不可能的,但它是使用 mixin 的好处之一。

特点和限制

与这些定义紧密匹配的模式的实现必须满足以下约束:

  • 我们可以向一个类添加多个 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 创建该对象。

这是满足所有约束的基本解决方案:

元典智库
元典智库

元典智库:智能开放的法律搜索引擎

下载
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 的这种实现并不完美(在我看来,如果没有语言级别的支持,任何一个都不可能是完美的)。特别是,它存在以下缺点:

  • 所有 mixin 方法都必须是公共的。一些 mixin 包含旨在由使用 mixin 的类使用的方法,以及其他如果从外部调用则更有意义的方法。由于 mixin 在接口上定义了其方法,因此不可能强制编译器验证这些约束。然后我们必须依赖文档或静态代码分析工具。
  • mixin 方法无法使用 mixin 访问类的实例。在委托声明时,实例尚未初始化,我们无法将其传递给 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

    companion object {
        private class holder : auditable {
            private val events = mutablelistof()
            override fun auditevent(event: string) {
                events.add(timestampedevent(instant.now(), event))
            }
            override fun getlatestevents(n: int): list {
                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 {
    fun subscribe(observer: (t) -> unit)
    fun notifyobservers(event: t)

    companion object {
        private class holder : observable {
            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  invoke(): observable = holder()
    }
}

sealed interface catalogevent
class priceupdated(val product: string, val price: int): catalogevent

class catalog(): observable by observable() {
    val products = mutablemapof()

    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 开发者工具包中一个有趣的工具,我鼓励您在自己的代码中探索此方法,同时了解限制和替代方案。


  1. 有趣的事实:kotlin 有一个 trait 关键字,但它已被弃用并已被 interface 取代(请参阅 https://blog.jetbrains.com/kotlin/2015/05/kotlin-m12-is-out/#traits -现在是接口)↩

  2. 基于 mixin 的继承↩

  3. 类和混入↩

  4. 具有风格的面向对象编程 ↩

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C++系统编程内存管理_C++系统编程怎么与Rust竞争内存安全
C++系统编程内存管理_C++系统编程怎么与Rust竞争内存安全

C++系统编程中的内存管理是指 对程序运行时内存的申请、使用和释放进行精细控制的机制,涵盖了栈、堆、静态区等不同区域,开发者需要通过new/delete、智能指针或内存池等方式管理动态内存,以避免内存泄漏、野指针等问题,确保程序高效稳定运行。它核心在于开发者对低层内存有完全控制权,带来灵活性,但也伴随高责任,是C++性能优化的关键。

10

2025.12.22

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

56

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

52

2025.11.27

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

56

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

52

2025.11.27

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1159

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

215

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2055

2025.12.29

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

54

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号