讲师中心 微信公众号

工具推荐

视频效率加速
剧云
剧云

中文短剧剧本制作平台

Laper
Laper

剧本策划到成片协作

Seko
Seko

AI短剧一键生成成片

updream

一键生成高质量推广成片

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

秋枫君_7757

秋枫君_7757

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

|

643人浏览过

|

来源于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 的编程主题的各种文章中找到定义(2、3、4)。这些定义还带来了类扩展的概念,而没有经典继承提供的父子关系(或 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<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 开发者工具包中一个有趣的工具,我鼓励您在自己的代码中探索此方法,同时了解限制和替代方案。


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

  2. 基于 mixin 的继承↩

  3. 类和混入↩

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

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

热门AI工具

更多
咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

相关专题

更多
Kotlin协程编程与Spring Boot集成实践
Kotlin协程编程与Spring Boot集成实践

本专题围绕 Kotlin 协程机制展开,深入讲解挂起函数、协程作用域、结构化并发与异常处理机制,并结合 Spring Boot 展示协程在后端开发中的实际应用。内容涵盖异步接口设计、数据库调用优化、线程资源管理以及性能调优策略,帮助开发者构建更加简洁高效的 Kotlin 后端服务架构。

391

2026.02.12

Java Kotlin协程与异步编程实战
Java Kotlin协程与异步编程实战

本专题围绕 Kotlin 协程与异步编程展开,讲解挂起函数、协程作用域、异步任务调度及并发控制方法。通过实际项目案例,帮助开发者提升后端服务性能,实现高效异步任务处理与稳定性保障。

199

2026.04.13

Kotlin Android开发教程大全
Kotlin Android开发教程大全

系统讲解 Google 官方推荐的 Android 开发首选语言 Kotlin 的核心知识与工程实践,涵盖 Kotlin 简洁语法(空安全/数据类/扩展函数/解构声明/作用域函数)与 Java 互操作、集合操作与函数式编程、协程(Coroutines)异步编程(suspend/launch/async/Flow)、Jetpack Compose 声明式 UI 开发(状态管理/组合函数/导航/动画)、MVVM / MVI 架构设计与 Vi

288

2026.06.03

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

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

106

2025.12.22

Rust异步编程与Tokio运行时实战
Rust异步编程与Tokio运行时实战

本专题聚焦 Rust 语言的异步编程模型,深入讲解 async/await 机制与 Tokio 运行时的核心原理。内容包括异步任务调度、Future 执行模型、并发安全、网络 IO 编程以及高并发场景下的性能优化。通过实战示例,帮助开发者使用 Rust 构建高性能、低延迟的后端服务与网络应用。

298

2026.02.11

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

642

2026.03.05

Rust 系统编程入门与实战
Rust 系统编程入门与实战

统讲解近年最受关注的系统级编程语言 Rust 的核心知识体系,涵盖 Rust 的安装与 Cargo 项目管理、变量绑定与不可变性设计哲学、所有权(Ownership)/ 借用(Borrowing)/ 生命周期(Lifetime)三大核心概念、结构体/枚举与模式匹配(match)、Trait 特征系统与泛型编程、错误处理(Result/Option)、智能指针(Box/Rc/Arc)、并发编程的无畏并发(Fearless Concurre

497

2026.05.29

Rust异步网络服务与Tokio Runtime深度实践
Rust异步网络服务与Tokio Runtime深度实践

本专题围绕 Rust 在高并发网络服务中的应用展开,系统讲解 Tokio 异步运行时、任务调度模型、Future 执行机制以及高性能网络编程方式。结合实际服务端案例,帮助开发者构建低延迟、高可靠的异步服务架构。

296

2026.06.29

Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

0

2026.11.12

热门下载

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

精品课程

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn