Programming in Scala (Second Edition) 读书笔记12 Trais

1.什么是Trait ?

Traits are a fundamental unit of code reuse in Scala. A trait encapsulates
method and field definitions, which can then be reused by mixing them into
classes. Unlike class inheritance, in which each class must inherit from just
one superclass, a class can mix in any number of traits.
Traits是Scala中代码复用的基本单元

类似Java中的接口,但是可以定义method和field

2. 第一个Trait, 具有哲学家特征的青蛙,哈哈

package chapter12

trait Philosophical {
  def philosophise() = println("I consume memory, therefor I am !")
}

class Frog extends Philosophical {
  override def toString = "green"
}

object TestMain {
  def main(args: Array[String]) {
    val frog = new Frog
    frog.philosophise()    //I consume memory, therefor I am !

  }
}

This trait is named Philosophical. It does not declare a superclass, so
like a class, it has the default superclass of AnyRef. It defines one method,
named philosophize, which is concrete.

默认继承 AnyRef

Once a trait is defined, it can be mixed in to a class using either the
extends or with keywords

用关键字: extends 或 with 将Trait混入类

You can use the extends keyword to mix in a trait; in that case you
implicitly inherit the trait’s superclass
当用extends 关键字时,该类隐式地继承了trait的父类

A trait also defines a type.

定义一个trait的同时也定义了一个新的类型

    val philo: Philosophical = new Frog
    philo.philosophise()

3.显式声明父类,重载trait的方法

package chapter12

trait Philosophical {
  def philosophise() = println("I consume memory, therefor I am !")
}

class Cat 

class Frog extends Cat with Philosophical {
  override def toString = "green"
  override def philosophise() = println("It ain‘t easy to being " + toString)
}

4.At this point you might philosophize that traits are like Java interfaces
with concrete methods, but they can actually do much more. Traits can, for
example, declare fields and maintain state. In fact, you can do anything in
a trait definition that you can do in a class definition, and the syntax looks
exactly the same, with only two exceptions.
Trait可以做class可以做的所有事情,只有两点例外

第一,trait 不能包含类参数,也就是说不能有构造函数

trait Philosophical(x: Int, y: Int) {
  def philosophise() = println("I consume memory, therefor I am !")
}  
// 编译无法通过

第二,使用super调父类方法的时候,class中是静态绑定的,trait中是动态绑定的

The other difference between classes and traits is that whereas in classes,
super calls are statically bound, in traits, they are dynamically bound. If
you write “super.toString” in a class, you know exactly which method
implementation will be invoked. When you write the same thing in a trait,
however, the method implementation to invoke for the super call is undefined when you define the trait. Rather, the implementation to invoke will
be determined anew each time the trait is mixed into a concrete class. This
curious behavior of super is key to allowing traits to work as stackable modifications

关于可堆叠的更改(stackable modification)后面会详细说明

5.再看一个例子

package chapter12

class Point(val x: Int, val y: Int)

trait Rectangular {
  def topLeft: Point
  def bottomRight: Point
  
  def left = topLeft.x 
  def right = bottomRight.x 
  def width = right - left
}

abstract class Component extends Rectangular

class Rectangle (val topLeft: Point, val bottomRight: Point) extends Rectangular

这样所有的组件和矩形都有了Rectangular提供的几何方法

6. Ordered Trait

package chapter12

trait Ordered[T] {
  def compare(that: T): Int
  def > (that: T) = compare(that) > 0
  def < (that: T) = compare(that) < 0
  def >= (that: T) = (this > that) || (this == that)
  def <= (that: T) = (this < that) || (this == that)
}

class Rational(val n: Int, val d: Int) extends Ordered[Rational] {
   def compare(that: Rational) = this.n * that.d  - this.d * that.n
   def equals(that: Rational) = (this.n * that.d - this.d * that.n) == 0
}

object TestMain {
  def main(args: Array[String]) {
    val r1 = new Rational(3, 4)
    var r2 = new Rational(1, 2)
    println(r1 == r2)
    println(r1 > r2)
    println(r1 <= r2)
  }
}

客户代码只需实现compare和equals, > < >= <=由trait实现

trait中的this在这里就是动态绑定的,同理super也是

7.接下来解释前面提到的 stackabe modification.首先实现一个整数队列

package chapter12

import scala.collection.mutable.ArrayBuffer

abstract class IntQueue {
  def put(x: Int)
  def get(): Int
}
class BasicIntQueue extends IntQueue {
  private val buf = new ArrayBuffer[Int]
  def put(x: Int) { buf += x}
  def get = buf.remove(0)
}

8.来看一个神奇的魔术

package chapter12

import scala.collection.mutable.ArrayBuffer

abstract class IntQueue {
  def put(x: Int)
  def get(): Int
}
class BasicIntQueue extends IntQueue {
  private val buf = new ArrayBuffer[Int]
  def put(x: Int) { buf += x}
  def get = buf.remove(0)
}

trait Doubling extends IntQueue {
  abstract override def put(x: Int) { super.put(x * 2)}
}

object TestMain {
  def main(args: Array[String]) {
    class MyQueue extends BasicIntQueue with Doubling
    val queue = new MyQueue
    queue.put(1)
    queue.put(3)
    println(queue.get)  
    println(queue.get)
  }
}

两个println的输出是多少? 不再是1,3, 而是 2,6.

在不改变类BasicIntQueue代码的情况下,加上一个trait改变了BaistIntQueue的行为。这样的更改就叫做堆叠式更改。加一点东西来改变新的功能,而不是更改原有的功能,这是一个很重要的开发原则。

9.解释上面的代码

The Doubling trait has two funny things going on. The first is that it declares
a superclass, IntQueue. This declaration means that the trait can only be
mixed into a class that also extends IntQueue. Thus, you can mix Doubling
into BasicIntQueue, but not into Rational.
首先注意到的一点是trait Doubling 继承了一个class. 如果一个trait继承了一个class A, 那么trait只能被混入同样继承了class A的class。Scala依然要遵循单继承原则

The second funny thing is that the trait has a super call on a method
declared abstract. Such calls are illegal for normal classes, because they
will certainly fail at run time. For a trait, however, such a call can actually
succeed. Since super calls in a trait are dynamically bound, the super call
in trait Doubling will work so long as the trait is mixed in after another trait
or class that gives a concrete definition to the method
其次要注意的是 trait 中的 put方法被修饰为 abstract override

之所以为abstract方法,是因为这个方法调用了父类的抽象方法。这在class中是不允许的。调用了抽象方法的方法本身也应该是抽象的方法。允许再trait中这样做是因为:能够在运行时绑定这个抽象方法的实现。原文是:so long as the trait is mixed in after another trait or class that gives a concrete definition to the method。也就是说:只要在另一个实现了改方法的trait或class之后混入这个trait就可以

10. 可能你依然觉得不可思议。我们来剖析一下scala的编译器会如何解释对 MyQueue.put的调用

首先BasicIntQueue实现了put方法,如果没有混入trait,执行的应该是BasicIntQueue的方法

scala编译器在寻找最终执行那个方法的时候,也遵循后来居上的原则。后来我们混入了trait Doubling,它抽象地实现了put方法,真正执行的逻辑代码是Doubling的put,之不过在执行前,将抽象方法替换成了BasicIntQueue实现的具体方法

一句话: 真正执行的,是用具体方法替换后的trait中定义的逻辑

时间: 2024-11-10 00:51:43

Programming in Scala (Second Edition) 读书笔记12 Trais的相关文章

Programming in Scala (Second Edition) 读书笔记15 case class and pattern matching

一个算术表达式包含: 数字,变量,二元操作符,一元操作符.用下面几个类来模拟它们 package chapter15 abstract class Expr case class Var(name: String) extends Expr case class Number(num: Double) extends Expr case class UnOp(operator: String, arg: Expr) extends Expr case class BinOp(operator: 

Programming in Scala (Second Edition) 读书笔记10

你肯定见过在控制台用字符打印图形的程序,这一章就从定义一个图形元素开始.我们的图形元素都是一些字符组成的矩形 abstract class Element {   def contents: Array[String]   def height: Int = contents.length   def width: Int = if (height == 0) 0 else contents(0).length } 上面定义的三个方法都没有参数,连小括号也省去了.这样的方法叫做:无参方法(par

Programming in Scala (Second Edition) 读书笔记21 隐式转化

1. There's a fundamental difference between your own code and libraries of other people: you can change or extend your own code as you wish, but if you want to use someone else's libraries, you usually have to take them as they are. 你可以改变或扩展自己的代码,但是对

Programming in Scala (Second Edition) 读书笔记7 内置控制结构

1. One thing you will notice is that almost all of Scala's control structures result in some value Scala的每种控制语句都是有值的 This is the approach taken by functional languages, in which programs are viewed as computing a value, thus the components of a progr

Programming in Scala (Second Edition) 读书笔记18 Stateful Object

1. In previous chapters, we put the spotlight on functional (immutable) objects 在前面的章节,我们把焦点放在了函数式对象上 We did so because the idea of objects without any mutable state deserves to be better known 函数式对象更容易被理解,它永远只有一种状态 However, it is also perfectly poss

Programming in Scala (Second Edition) 读书笔记13 packages and import

目前,在一个包中你看到的top level的对象只有:class, trait, object.其实任何对象都可以是top level的.也就是说,没有必要把函数,value, variable等限制在class, trait, object中.它们可以在整个包范围内都是全局性的. 方法很简单,把这些东东放到package object中就行了.pakcage object的名字和包名相同 包含package object的文件放在该packag下,名字就叫package.scala chapt

Programming in Scala (Second Edition) 读书笔记6 函数和闭包

When programs get larger, you need some way to divide them into smaller, more manageable pieces. For dividing up control flow, Scala offers an approach familiar to all experienced programmers: divide the code into functions. In fact, Scala offers sev

Programming in Scala (Second Edition) 读书笔记26 Extractors

1. By now you have probably grown accustomed to the concise way data can be decomposed and analyzed using pattern matching. This chapter shows you how to generalize this concept further. Until now, constructor patterns were linked to case classes. Fo

Programming in Scala (Second Edition) 读书笔记3

创建并初始化一个String Array     val strArr1 = new Array[String](3)     strArr1(0) = "How"     strArr1(1) = "are"     strArr1(2) = "you!" 上述代码被transform 为     val strArr1 = new Array[String](3)     strArr1.update(0, "How")