scala学习笔记 - 高级类型(二)

自身类型

特质可以要求混入它的类扩展自另一个类型,用自身类型(self type)的声明来定义特质:

this: 类型 =>

这样的特质只能被混入给定类型的子类当中。在如下示例中,LoggedException特质只能被混人扩展自 Expcetion的类:

trait Logged { 
  def log(msg: String) 
}
trait LoggedException extends Logged{ 
  this: Exception => 
    def log(){ log(getMessage()) } 
      //可以调用getMessage ,因为this是一个Exception
}

如果尝试将该特质混入不符合自身类型所要求的类,就会报错:

val f = new JFrame with LoggedException 
// 错误:JFrame不是LoggedException的自身类型Exception的子类型

如果想要提出多个类型要求,可以用复合类型:

this T with U with ... =>

注意:自身类型并不会自动继承。如果像如下这样定义:

trait ManagedException extends LoggedException{...}

就会得到错误提示,说ManagedException 并未提供Exception 。在这种情况下,你需要重复自身类型的声明:

trait ManagedException extends LoggedException { 
  this: Exception =>
    ...
}

依赖注入

在Scala中,可以通过特质和自身类型达到一个简单的依赖注入的效果。对日志功能而言,假定我们有如下特质:

trait Logger{ def log(msg: String) }

有该特质的两个实现:ConsoleLogger和FileLogger。
用户认证特质有一个对日志功能的依赖,用于记录认证失败:

trait Auth { 
  this: Logger => 
    def login(id: String, password: String): Boolean
}

应用逻辑有赖于上述两个特质:

trait App { 
  this: Logger with Auth =>
    ...
}

然后,就可以像这样来组装自己的应用:

object MyApp extends App with FileLogger("test.log") with MockAuth("users.txt")

像这样使用特质的组合有些别扭。毕竟,一个应用程序并非是认证器和文件日志器的合体。它拥有这些组件,更自然的表述方式可能是通过实例变量来表示组件,而不是将组件黏合成一个大类型。蛋糕模式(cake pattern)给出了更好的设计,在这个模式当中,你对每个服务都提供一个组件特质,该特质包含:

  • 任何所依赖的组件,以自身类型表述。
  • 描述服务接口的特质。
  • 一个抽象的val,该val将被初始化成服务的一个实例。
  • 可以有选择性地包含服务接口的实现。
trait LoggerComponent{
	trait Logger { ... }
	val logger: Logger 
	class FileLogger(file : String) extends Logger{ ... }
	...
} 
trait AuthComponent{ 
	this: LoggerCompoent => // 让我们可以访问日志
	trait Auth{ ... } 
	val auth: Auth 
	class MockAuth (file: String) extends Auth{ ... }
	...
}

注意我们对自身类型的使用,这段代码用来表明认证组件依赖日志器组件。
这样一来 ,组件配置就可以在一个集中的地方完成:

object AppComponents extends LoggerComponent with AuthComponent { 
	val logger = new FileLogger("test.log") 
	val auth = new MockAuth("users.txt")
}

不论是上述哪一种方式,都比在XML文件中组装组件要好,因为编译器可以帮我们校验模块间的依赖是被满足的。

抽象类型

类或特质可以定义一个在子类中被具体化的抽象类型(abstract type)例如:

trait Reader{ 
  type Contents 
  def read(fileName: String): Contents
}

在这里,类型Contents抽象的。具体的子类需要指定这个类型:

class StringReader extends Reader { 
  type Contents = String 
  def read(fileName: String) = Source.fromFile(fileName, "UTF-8").mkString
}

class ImageReader extends Reader { 
  type Contents = Bufferedlmage 
  def read(fileName: String) = ImageIO.read(new File(fileName))
}

同样的效果也可以通过类型参数来实现:

trait Reader[C]{ 
  def read(fileName: String): C
}

class StringReader extends Reader[String]{ 
  def read(fileName: String) = Source.fromFile(fileName, "UTF- 8").mkString
}
class ImageReader extends Reader[Bufferedirnage]{ 
  def read(fileName: String) = ImageIO.read(new File(fileName))
}

哪种方式更好呢?Scala的经验法则是:

  • 如果类型是在类被实例化时给出的,则使用类型参数。比如构造HashMap[String, Int],你会想要在这个时候控制使用的类型。
  • 如果类型是在子类中给出的,则使用抽象类型。我们的Reader实例就挺符合这个描述的。
    在构建子类时给出类型参数并没有什么不好,但是当有多个类型依赖时,抽象类型用起来更方便一一你可以避免使用一长串类型参数。例如:
trait Reader { 
  type In 
  type Contents 
  def read(in: In): Contents
}
class ImageReader extends Reader { 
  type In = File 
  type Contents = BufferedImage 
  def read(file: In) = ImageIO.read(file)
}

用类型参数的话,ImageReader就需要扩展自Reader[File, BufferedImage]。这依然没问题,但你可以看得出来,在更复杂的情况下,这种方式的伸缩性并不那么好。
并且,抽象类型还能够描述类型间那些微妙的相互依赖。
抽象类型可以有类型界定,这就和类型参数一样。例如:

trait Listener { 
  class EventObject{...}
  type Event <: EventObject
  ...
}

子类必须提供一个兼容的类型,比如:

trait ActionListener extends Listener{ 
  class ActionEvent extends EventObject{...} 
  type Event = ActionEvent // OK ,这是一个子类型
  ...
}

注意:如果用类型参数,这个例子是无法完成的,因为绑定的是 个内部类。

家族多态

如何对那些跟着一起变化的类型家族建模,同时共用代码,并保持类型安全,是一个挑战。举例来说,我们可以看一下客户端Java的事件处理;这里有不同种类的事件(比如ActionEvent、ChangeEvent 等),而每种类型都有单独的监听器接口(ActionListener、ChangeListener等)。这就是“家族多态”的典型示例。
让我们来设计一个管理监听器的通用机制;我们首先用泛型类型,之后再切换到抽象类型。
在Java中,每个监听器接口有各自不同的方法名称对应事件的发生:actionPerformed、stateChanged、itemStateChanged等。 我们将把这些方法统一起来:

trait Listener[E]{ 
  def occurred(e: E): Unit
}

而事件源需要一个监听器的集合和一个触发这些监听器的方法:

trait Source[E, L <: Listener[E]] { 
  private val listeners = new ArrayBuffer[L] 
  def add(l: L){ listeners += l } 
  def remove(l: L){ listeners -= l } 
  def fire(e: E){
    for (l <- listeners ) l.occurred(e)
  }
}

现在,我们来考虑按钮触发动作事件的情况 我们定义如下监听器类型:

trait ActionListener extends Listener[ActionEvent]

Button类可以混入Source特质:

class Button extends Source[ActionEvent, ActionListener]{ 
  def click(){ 
    fire(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "click"))
  }
}

任务完成:Button类不需要重复那些监听器管理的代码,并且监听器是类型安全的,你没法给按钮加上ChangeListener。
ActionEvent类将事件源设置为this,但是事件源的类型为Object。我们可以用自身类型来让它也是类型安全的:

trait Event[S]{ 
  var source: S = _
}
trait Listener[S, E <: Event[S]] { 
  def occurred(e: E): Unit
}
trait Source[S, E <: Event[S], L <: Listener[S, E]] { 
  this: S => 
  private val listeners = new ArrayBuffer[L]
  def add (l: L) { listeners += l ) 
  def remove (l: L) { listeners -= l } 
  def fire (e: E) { 
    e.source = this // 这里需要自身类型
    for (l <- steners) l.occurred(e)
  }
}

注意自身类型this: S =>,其用来将事件源设为this,否则,this只能是某种Source,而并不一定是Event[S]所要求的类型。
以下代码展示了如何定义一个按钮:

class ButtonEvent extends Event[Button] 
trait ButtonListener extends Listener[Button , ButtonEvent] 
class Button extends Source[Button, ButtonEvent, ButtonListener]{ 
  def click(){ fire(new ButtonEvent) }
}

你可以看到类型参数扩张得很厉害,如果用抽象类型,代码会好看一些。

trait ListenerSupport { 
  type S <: Source 
  type E <: Event 
  type L <: Listener 
  trait Event { 
    var source: S = _
  }
  trait Listener { 
    def occurred (e: E): Unit
  }
  trait Source { 
    this : S => 
    private val listeners = new ArrayBuffer[L]
    def add(l: L) { steners += 1 }
    def remove(l: L) { listeners -= 1 } 
    def fire (e: E) {
      e.source = this 
      for (l <- steners) l.occurred(e)
    }
  }
}

但所有这些也有代价:你不能拥有顶级的类型声明,这就是所有代码都被包在ListenerSupport特质里的原因。
接下来,当你想要定义一个带有按钮事件和按钮监听器的按钮时,就可以将定义包含在一个扩展该特质的模块当中:

object ButtonModule extends ListenerSupport { 
  type SourceType = Button 
  type EventType = ButtonEvent 
  type ListenerType = ButtonListener 
  class ButtonEvent extends Event 
  trait ButtonListener extends Listener 
  class Button extends Source { 
    def click(){ fire(new ButtonEvent) }
  }
}

如果要用这个按钮,引入这个模块即可:

object Main{ 
  import ButtonModule._
  def main(args: Array[String]){ 
    val button = new Button 
    button.add(new ButtonListener{ 
      override def occurred(e: ButtonEvent) { println(e) } 
    }) 
    button.click()
  }
}

高等类型

泛型类型List依赖于类型T并产出一个特定的类型 。举例来说,给定类型Int ,得到的是类型List[Int],因此,像List这样的泛型类型有时被称作类型构造器(type constructor)。在Scala中,可以更上一层,定义出依赖于依赖其他类型的类型的类型。
要搞清楚为什么这样做是有意义的,可以看看如下简化过的Iterable特质:

trait Iterable[E] { 
  def iterator(): Iterator[E] 
  def map[F](f: (E) => F): Iterable[F]
}

现在,考虑一个实现该特质的类:

class Buffer[E] extends Iterable[E]{ 
  def iterator() : Iterator[E] = ...
  def map[F] (f: (E) => F): Buffer[F] = ...
}

对于Buffer,我们预期map方法返回一个Buffer,而不仅仅是Iterable,这意味着我们不能在Iterable特质中实现这个map方法。一个解决方案是使用类型构造器来参数化Iterable,就像这样:

trait Iterable[E, C[_]] { 
  def iterator(): Iterator[E] 
  def build[F](): C[F] 
  def map[F] (f: (E) => F): C[F]
}

这样一来,Iterable就依赖一个类型构造器来生成结果,以C[ _ ]表示。这使得Iterable成为一个高等类型。
map方法返回的类型可以是,也可以不是与调用该map方法的原Iterable相同的类型,举例来说,如果你对Range执行map方法,结果通常不会是另一个区间,因此map必须构造出另一种类型,比如Buffer[F]。这样的一个Range类型声明如下:

class Range extends Iterable[Int, Buffer]

注意第二个参数是类型构造器Buffer。
要实现Iterable中的map方法,我们需要寻求更多的支持。Iterable需要能够产出一个包含了任何类型F的值的容器。我们定义一个Container类,某种你可以向它添加值的东西:

trait Container[E] { 
  def +=(e: E): Unit
}

而build方法被要求交出这样一个对象:

trait Iterable[E, C[X] <: Container[X]] { 
  def build[F](): C[F]
  ...
}

类型构造器C现在被限制为一个Container,因此我们知道可以往build方法返回的对象添加项目,我们不再对参数C使用通配符,因为我们需要表明C[X]是一个针对同样的X的容器。
有了这些以后,我们就可以在Iterable特质中实现map方法了:

def map[F](f: (E) => F): C[F] = { 
  val res = build[F]() 
  val iter = iterator() 
  while (iter.hasNext) res+= f(iter.next()) 
  res
}

这样一来,可迭代(Iterable)的类就不再需要提供它们自己的map实现了。如下是Range类的定义:

class Range(val low: Int, val high: Int) extends Iterable[Int, Buffer] { 
  def iterator() =new Iterator[Int] { 
    private var i = low 
    def hasNext = i <= high 
    def next() = { i += 1; i - 1 }
  }
  def build[F] () = new Buffer[F]
}

注意Range是一个Iterable:你可以遍历其内容。但它并不是一个Container:不能对它添加值。而Buffer不同,它既是Iterable,也是Container。

class Buffer[E: ClassTag] extends Iterable[E, Buffer] with Container[E] { 
  private var capacity = 10 
  private var length = 0
  // 为了构造出泛型的Array[E],类型E必须满足ClassTag上下文界定
  private var elems = new Array[E](capacity) 
  def iterator() = new Iterator[E] { 
    private var i = 0 
    def hasNext = i < length 
    def next() = { i += 1; elems(i - 1) } 
  }
  def build[F: ClassTag]() = new Buffer[F]
  def +=(e: E) { 
    if (length == capacity) { 
      capacity = 2 * capacity 
      val nelems = new Array[E](capacity)
      for (i <- 0 until length) nelems(i) = elems(i) 
      elems = nelems 
    }
    elems(length) = e 
    length += 1
  }
}

说明:要不带警告地使用高等类型,可添加如下的引入语句import scala.language.higherKinds或使用编译器选项-language:higherKinds
上述示例展示了高等类型的典型用例。Iterator依赖Container,但Container不是一个普通的类型,它是制作类型的机制。
Scala集合类库中的Iterable特质并不带有显式的参数用于制作集合,而是使用隐式参数来带出一个对象用于构建目标集合。
参考:快学scala(第二版)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值