混入(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
0val/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 BOOMwith 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注意
- 混入成员是编译期克隆,运行时不存在混入实例。
- 混入不能被实例化、不能当类型用,也没有静态命名空间。
- 需要运行时多态(协议位流动)就用协议,需要行为复用就用混入。
