Traits
Traits 是用于创建class的模板,同样的原理,class是创建对象的模板. trait和java中interfaces类似,可以把trait看做是接口和抽象类.
Trait声明语法:
trait TraitName {
declarationOrExpression
...
}
class Name(...) extends TraitName {
...
}
case class Name(...) extends TraitName {
...
}
Trait和Class的不同
和class一样,trait同样是一组字段和方法的定义,但是有几点重要的不同:
- trait不能有构造器,不能直接从一个trait创建对象,可以从一个trait创建一个class,然后从一个class创建对象.
- trait可以定义抽象方法:只有名字和类型签名但是没有具体实现.
This or That and Nothing Else: Sealed Traits
在许多用例中,我们可以枚举能够继承一个trait的所有class. 比如构建网站用户时可以设定匿名用户和已登录用户,这两种用户基本涵盖了用户类别的所有可能,因此我们可以创建一个sealed trait,来让编译器帮助我们进行类型检查:
sealed trait Visitor {
def id: String
def createdAt: Date
def age: Long = new Date().getTime() - createdAt.getTime()
}
在使用sealed trait时必须将其子类型也定义在同一个文件内,一旦一个trait被声明为sealed,模式匹配中必须涵盖该trait的所有子类型,否则会发出警告.
sealed trait Visitor { /* ... */ }
final case class User(/* ... */) extends Visitor
final case class Anonymous(/* ... */) extends Visitor
闭合特质的声明语法:
sealed trait TraitName {
...
}
// 所有继承自该trait的子类型必须定义在同一个文件内
final case class Name(...) extends TraitName {
...
}
Modelling Data with Traits
The Product Type Pattern
第一种模式是为一个包含其他类型数据的数据格式进行建模.
产品类型模式的一般语法:
// If A has a b(with type B) and a c(with type C) write
case class A(b: B, c: C)
trait A {
def b: B
def c: C
}
The Sum Type Pattern
这种模式是,一个数据结构属于多个不同的类型.比如”A是一个B,或是一个C”.
sealed trait A
final case class B() extends A
final case class C() extends A
Working With Data
Structural Recursion using Polymorphism
使用多态结构化递归.
如果在trait中声明一个抽象方法,然后在继承自它的两个子类中分别以不同的方式实现该方法,当我们调用该方法是实际上调用的是该实例对应的具体实现.
sealed trait A {
def foo: String
}
final case class B() extends A {
def foo: String = "It's B!"
}
final case class C() extends A {
def foo: String = "It's C!"
}
当声明一个类型为A的值时,我们可以到具体的实现却是用的B或C:
scala> val anA: A = B()
anA: A = B()
scala> anA.foo
res1: String = It's B!
scala> val anA: A = C()
anA: A = C()
scala> anA.foo
res2: String = It's C!
同样可以在trait中定义一个实现,然后在子类中使用override关键字重写该实现:
sealed trait A {
def foo: String = "It's A!"
}
final case class B() extends A {
override def foo: String = "It's B!"
}
final case class C() extends A {
override def foo: String = "It's C!"
}
行为会和之前保持一致,仍然会选择具体类的实现:
scala> val anA: A = B()
anA: A = B()
scala> anA.foo
res3: String = It's B!
记住如果在trait中提供了一个默认的实现,那就要保证该实现对所有的子类型都适用.
Structural Recursion using Pattern Matching
使用模式匹配格式化递归.
The Product Type Pa ern Matching Pattern:
// If A has a b(with type B) and a c(with type C),
// and we want to write a method f that accepts an A and returns an F, write
def f(a: A): F = a match {
case A(b, c) => ???
}
The Sum Type Pa ern Matching Pattern:
// If A is a B or C, and we want to write a method f accepting an A and returning an F,
// define a pattern matching case for B and C.
def f(a: A): F = a match {
case B() => ???
case C() => ???
}
A Complete Example
数据描述,一个Feline是一个Lion,Tiger,Panther或者Cat.我们将会简化数据的描述,一个Cat只有一个String名为favoriteFood:
sealed trait Feline
final case class Lion() extends Feline
final case class Tiger() extends Feline
final case class Panther() extends Feline
final case class Cat(favouriteFood: String) extends Feline
现在我们同时使用泛型和模式匹配来实现一个方法. dinner方法将会在问题中返回适合feline的食物,Cat的dinner是favoriteFood,Lion的是antelope,Tiger是 tiger food,Panters是licorice.
然后定义食物类型:
sealed trait Food
final case object Antelope extends Food
final case object TigerFood extends Food
final case object Licorice extends Food
final case class CatFood(food: String) extends Food
然后定义dinner方法返回Food,首先使用泛型:
sealed trait Feline {
def dinner: Food
}
final case class Lion() extends Feline {
def dinner: Food = Antelope
}
final case class Tiger() extends Feline {
def dinner: Food = TigerFood
}
final case class Panther() extends Feline {
def dinner: Food = Licorice
}
final case class Cat(favouriteFood: String) extends Feline {
def dinner: Food = CatFood(favouriteFood)
}
现在使用模式匹配,使用模式匹配实现时有两种选择,一种是直接在Feline中用一个单独的方法实现,一种是在另一个object中实现:
sealed trait Feline {
def dinner: Food = this match {
case Lion() => Antelope
case Tiger() => TigerFood
case Panther() => Licorice
case Cat(favouriteFood) => CatFood(favouriteFood)
}
}
object Diner {
def dinner(feline: Feline): Food = feline match {
case Lion() => Antelope
case Tiger() => TigerFood
case Panther() => Licorice
case Cat(food) => CatFood(food)
}
}
Choosing Which Pattern to Use
现在我们有三种方式实现结构化递归:
- 泛型
- 在trait中模式匹配
- 在一个单独的object中模式匹配
如果一个方法依赖于class的参数或内置方法,则在class内部进行实现.
如果一个方法依赖于其他外部数据(比如需要Cook产生dinner),则在class的外部实现.
如果需要多种具体实现,则需要使用模式匹配并在class的外部实现.
Object-Oriented vs Func onal Extensibility
Add new method | Add new data | |
---|---|---|
OO | Change existing code | Existing code unchanged |
FP | Exising code unchanged | Change existing code |