Skip to Content

混入(Mixin)

mixin 是一组可复用成员的声明块,编译期整块克隆进挂载它的类。它解决「多个不相干的类需要同一组方法」的问题,不占用唯一的继承位。

完整声明形态是 mixin Name<T> requires R1, R2 impl P1, P2 where T: Ord { },requires、impl、where 按需组合。

声明与挂载

mixin Noisy { public func Noise() -> string => "boom"; public func Twice() -> string => self.Noise() + self.Noise(); } class SilentBoom with Noisy { public func Noise() -> string => "自家的"; } val s = new SilentBoom(); print(s.Noise(), s.Twice());
自家的 自家的自家的

class ... with Noisy 把混入成员克隆进来。克隆体里 self.Noise() 走的是宿主对象的方法表:宿主自己的同名方法遮蔽混入版本,Twice 也跟着用上了宿主的 Noise。

能混入什么

混入的可声明范围:类型体能声明的成员种类,混入体基本都能带。方法之外,字段、属性、static 成员、转换运算符、事件(含管线段)都整块克隆进宿主:

mixin Metered { public var hits: int = 0; public val tag: string = "仪表"; public prop Twice: int => hits * 2; public static var built: int = 0; public func Bump() -> int { hits += 1; return hits; } } class Counter2 with Metered { } val c = new Counter2(); print(c.Bump(), c.Bump(), c.Twice, c.tag); print(new Counter2().hits); print(Counter2.built);
1 2 4 仪表 0 0
  • val / var 字段克隆进宿主的实例布局,每个实例独立一份:c.Bump() 走自己的 hits,新实例从 0 起步。
  • prop 全形态照搬(自动属性、表达式体、computed;static prop 每宿主各一份存储),混入方法体内对 prop 的裸名读、写与复合赋值(Level = Level + 3)自动路由到宿主的访问器与背槽。
  • static 成员同样克隆,经宿主类型名访问(Counter2.built),混入自身没有静态命名空间,不可以使用 混入名.成员。
  • const 克隆后挂宿主类型位:宿主名.常量 与实例位读都可用。
  • event / event prop 随克隆进宿主订阅面:宿主位 ~> 订阅、混入方法内 emit 派发、event prop 写位全管线广播照常,管线段(before / after / finally)也随克隆发射,语义与宿主体声明的管线同律,多个宿主各自发射互不串扰;
  • init 与 deinit 不能出现在混入体:构造与析构是宿主自己的生命周期。

混入继承

混入只能继承混入(mixin A : B),继承链上的成员一并克隆:

mixin Loud : Noisy { public func Louder() -> string => self.Noise().ToUpper(); } protocol GreeterX { func Greet() -> string; } class Boombox with Loud impl GreeterX { public func Greet() -> string => "轰"; } val b = new Boombox(); print(b.Greet(), b.Noise(), b.Twice(), b.Louder());
轰 boom boomboom BOOM

with Loud 把 Loud 与它继承的 Noisy 一起带入,宿主同时拥有四个成员,还能照常 impl 协议、参与继承。

requires:挂载前置条件

protocol LogSource { func Log() -> string; } mixin Echo requires LogSource { public func Shout() -> string => self.Log() + "!"; } class Event2 impl LogSource with Echo { public func Log() -> string => "事件"; } print(new Event2().Shout());
事件!

requires 声明宿主必须满足的条件才能 with 它,三类目标:协议、基类、别的混入。缺条件时挂载报错,把「这套方法假定宿主有什么」写成了编译期契约。

混入与协议

混入体可以带 impl 子句,但它只校验混入体自身(成员的 self 按混入类型比对),不会让宿主自动满足协议:宿主要作协议位流动,得自己写 impl 并自备同形成员。

想让既有类型不写 impl 就满足协议,用扩展的 extension T impl P 形态,见扩展的「扩展与协议」。

冲突与消歧

两个混入带来同名成员,可以用 hiding 隐藏其一,rename 给其中一个改名,两表顺序不限可组合:

mixin Pair2 { public func Kick() -> string => "踢"; public func Punch() -> string => "打"; } mixin Extra { public func Kick() -> string => "双踢"; } class Fighter with Pair2, Extra rename { Kick => DoubleKick } { } class Guard with Pair2, Extra hiding { Kick } { } val f = new Fighter(); print(f.DoubleKick(), f.Punch()); val g = new Guard(); print(g.Kick(), g.Punch());
双踢 打 踢 打

rename 后两个成员都在(Kick 与 DoubleKick)。hiding 则把 Extra.Kick 整个丢弃,Kick 只剩 Pair2 的版本。

泛型混入

mixin Boxed<T> { public func Wrap(v: T) -> string => $"装 {v}"; } class Crate2 with Boxed<int> { } print(new Crate2().Wrap(9));
装 9

挂载时给定类型实参(with Boxed<int>),混入类型参数同样可以带 where 约束:

import Math.*; mixin Shouter<T> where T: Numeric { public func Show(v: T) -> string => $"值 {v}"; } class Box2 with Shouter<int> { } print(new Box2().Show(5));
值 5

注意

  • 混入成员是编译期克隆,运行时不存在混入实例。
  • 混入不能被实例化、不能当类型用,也没有静态命名空间。
  • 需要运行时多态(协议位流动)就用协议,需要行为复用就用混入。
最后更新于 2026年10月9日