今天有人吐槽scala里面的traitabstrct class不是一样的嘛。当着这么多人的面又不能吐槽,只能自己私底下抱怨了。

一般的mixin和trait

首先,不论是mixin还是trait都是为了解决多继承问题产生的,在c++中,经常吐槽的就是多继承带来的冲突,以及虚基类菱形继承结构带来的一些问题(比如这种情况下,虚基类的对象在子类对象中应该只存在一个什么的)。所以很多语言干脆就禁止多继承,比如但是interface一般是不允许实现的,只声明接口。不过java8现在也允许在接口中提供default的实现了。

一般来说,(嗯,一般来说) - mixin的方式允许保留状态,其实就是可以有成员变量,而traits都是方法。(嗯,这条scala就不满足)

  • mixin会隐式的解决冲突,即如果继承多个类有同名函数的话,会自动的按照一定规则解决冲突,但是,一般trait会报错。

  • 这一条最关键,我感觉最有用的,就是在处理函数的依赖的时候,mixinlinearization,而traitflattened,或者我感觉更像是dynamic的。这一条,之后的栗子就可以看出来。

SIP 25

今年的纽约的scaladay上,马丁也指出scala要支持trait parameters这意味着只从语法上来讲traitabstrct class的差距越来越小,而他本人也说希望traitabstrct class成为two side of the same thing

同时对于

1
2
3
4
5
6
trait T(x: A)
trait U extends T

// 以下两者等价
class C extends T(e) with U
class C extends U with T(e)

这就要求如果一个class C继承一个继承了有参trait的trait的话,他要直接把有参的trait加到自己的父类上来。

一个例子

trait有什么用,下面看一个具体的例子。

假设我们需要这么一个东西。对于一个web服务器提供的服务,返回相应的信息。同时,服务器提供了多种连接方式,有普通的http也有web socket。对于接受消息到对应的相应这部分,我们希望提供好看的语法。

一半来说,我们把多种方式抽象一层,称为Connect,每次启动的时候,注入具体的连接方式到提供方便语法的类中。

1
2
3
4
5
6
7
8
9
class ServerWithDSL(connect: Connect) {
// 提供dsl语法
import ServerDSL._
def run(): Unit = ???
}

object ServerWithDSL {
... // 简化构造过程
}

我们可以利用trait构造一个不同的语法。首先一个可以运行的Server确实是应该同时之后如何通信以及对外暴露的语法的。所以,具体怎么run应该是因连接方式不同而不同点。干脆就

1
2
3
4
5
6
7
8
trait Server {

// 在已经有了连接的情况下可以实现的部分
// 可以是纯函数或者副作用在参数中体现的
def handlerUpdate(): Unit
def handlerXXX(): Unit
def run(): Unit
}

然后分别实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import scala.collection.mutable

type Message = String
case class Update(key: String, msg: List[Message])

trait Server {
// some common function
def handleUpdate(update: Update): Unit =
handleMsg(update)

def handleMsg(update: Update): Unit

def run(): Unit
}

trait WebSocket extends Server {
def run(): Unit = {
println("run with websockets")

// some web socket code
val update = Update("add", List("on", "on", "on"))
handleUpdate(update)
val update1 = Update("remove", List("off", "off", "off"))
handleUpdate(update1)
}
}

trait LongPoll extends Server {
def run(): Unit = {
println("run with long poll")

val update = Update("add", List("on", "on", "on"))
handleUpdate(update)
val update1 = Update("remove", List("off", "off", "off"))
handleUpdate(update1)
}
}

trait OnDSL extends Server {
type ReplayAction = Unit
type Action = Message => ReplayAction

private val actions = mutable.Map[String, Action]()

val defaultAction = (x:String) => println(s"not define response for $x")

def on(key: String)(action: Action): Unit = {
actions(key) = action
}

override def handleMsg(update: Update): Unit = {
val f = actions.getOrElse(update.key, defaultAction)
update.msg map f
}
}

object Main extends Server with WebSocket with OnDSL {
on("add") {
x => println(s"add $x")
}

on("remove") {
x => println(s"remove $x")
}
}

Main.run()

应用的时候通过混入不同的trait来选择不同的连接方法以及方便的语法,来分离职责。这也是由于trait的中的函数是动态绑定的,会根据你混入的类的实现来选择。当然,有冲突的情况是显示处理的。

通过混入的方法,把一个类中正交的部分分离出来,然后分别实现,最后通过混入来完成。




X