Desugar Scala(18) -- stackable traits
Stackable traits是一种怎样的特性呢?
来举一个🌰
1 | abstract class IntQueue { |
定义一个IntQueue,抽象类,定义了get和put,没有实现。
1 | class BasicIntQueue extends IntQueue { |
再定义一个BasicIntQueue,把上述IntQueue实现了。
它的实现没有什么花样,就是先进先出。
接下来就有意思了:
1 | trait Incrementing extends IntQueue { |
定义了两个trait,都扩展自IntQueue。
一个是把数字先加一再放进队列,另一个是先把数字加倍再放入队列。
要注意这里的modifier:abstract override,以及在trait中对super的调用。稍后反编译的时候可以看懂它们的真实含义。
那这两个trait可以怎么使用呢?
1 | class MagicQueue extends BasicIntQueue with Incrementing with Doubling |
定义一个MagicQueue,它扩展自BasicIntQueue,同时mixin了上面的两个trait。
MagicQueue它自己是一行实现代码都没有的,那么它的行为会是什么样子呢?
1 | val queue = new MagicQueue |
可以看到,它会先把数字乘以二,然后加一再放入队列。
MagicQueue继承了BasicIntQueue,混入了Incrementing和Doubling,它的行为就会是先跑Doubling后跑Incrementing最后跑BasicIntQueue(从右到左依序生效)。
这是种很实用的语言特性,你可以写很多个不同的trait,让它们都extend IntQueue。
同时写很多class让它们实现IntQueue。
然后每一个实现了IntQueue的class都可以和任意一个或者任意多个trait随意组合应用。
这给语言的使用者提供了很强的composition的便利性。
那下面看下这个语言特性是如何实现的。
1 | public abstract class IntQueue |
首先,IntQueue和BasicIntQueue反编译之后平淡无奇,一个抽象类,一个实现类。
1 | public interface Doubling |
Doubling这个trait则被编译成了一个接口加一个抽象类,其中除了put之外还有一个名字有点奇怪的方法声明。
稍后可以看到它有什么用。
1 | public interface Incrementing |
Incrementing则和Doubling是一个路数。
(这里出现的chap12字样是我写代码时package的名字)
最后揭露真相的时候到了:
1 | public class MagicQueue extends BasicIntQueue implements Incrementing, Doubling |
MagicQueue本身被编译成了以上的样子。
我们看一下它的put方法被调用时会怎样呢?
它去调用Doubling$class.put这个静态方法,把自己和数字都传入
Doubling$class.put则会先把数字乘以二,然后把乘积传给MagicQueue的chap12$Doubling$$super$put
MagicQueue的chap12$Doubling$$super$put方法则会把MagicQueue自己的实例以及乘积都传给Incrementing$class.put这个静态方法
Incrementing$class.put则会把接收到的参数,也就是乘积,加一,然后把加和后的数字传给MagicQueue的chap12$Incrementing$$super$put
MagicQueue的chap12$Incrementing$$super$put最终把乘以二又加了一的数字传给了super.put
super.put其实就是BasicIntQueue.put了,到这里终于把数字存到ArrayBuffer里面了
这样,Doubling,Incrementing,BasicIntQueue它们三个的行为就堆叠(stackable)在一起了。