Scala的自定义类型标记

Scala中有很多千奇百怪的符号标记,看起来是那么的独特,就像是一杯dry martini…好像黑夜中的萤火虫,那么耀眼,那么出众。

好了言归正传,这一篇文章我们会讲一下Scala中的自定义类型标记,通过自定义类型标记可以将this指向额外的类型期望。

我们先看一个观察者模式的例子:

  1. abstract class SubjectObserver {
  2. type S <: Subject // <1>
  3. type O <: Observer // <2>
  4. trait Subject {
  5. private var observers = List[O]() // <3>
  6. def addObserver(observer: O) = observers ::= observer
  7. def notifyObservers() = observers.foreach(_.receiveUpdate(this)) // <4>
  8. }
  9. trait Observer {
  10. def receiveUpdate(subject: S) // <5>
  11. }
  12. }

分析下上面的例子,我们在一个类中同时定义了Subject和Observer, 因为Subject和Observer是trait,而不是一个世纪的类型,所以我们又定义了Subject和Object为S和O的类型上界,这就意味着S和O分别是Subject和Object的子类型。

在4的位置,notifyObservers需要通知存储在Subject中的observers,调用Observer的receiveUpdate方法。

receiveUpdate需要接受一个具体的子类型S,但是4的位置receiveUpdate(this)中传递的参数是this即Subject,这样会导致编译失败。

那么如果我们想实现在Subject中传递S类型的实例怎么办?这时候就可以使用到自定义类型标记了。

我们看下面改造的例子:

  1. abstract class SubjectObserver {
  2. type S <: Subject
  3. type O <: Observer
  4. trait Subject {
  5. self: S => // <1>
  6. private var observers = List[O]()
  7. def addObserver(observer: O) = observers ::= observer
  8. def notifyObservers() = observers.foreach(_.receiveUpdate(self)) // <2>
  9. }
  10. trait Observer {
  11. def receiveUpdate(subject: S): Unit
  12. }
  13. }

变化的点在1和2,位置1定义了一个自定义类型标记,它说明了两个意思:

  1. self指向了this
  2. self是S类型的实例

在2中,我们直接传入self就行了,这里self也可以换做其他的字面量。

下面我们看下怎么使用这个类:

  1. case class Button(label: String) {
  2. def click(): Unit = {} // <1>
  3. }
  4. object ButtonSubjectObserver extends SubjectObserver {
  5. type S = ObservableButton // <2>
  6. type O = Observer
  7. class ObservableButton(label: String) extends Button(label) with Subject {
  8. override def click() = {
  9. super.click()
  10. notifyObservers()
  11. }
  12. }
  13. }
  14. import ButtonSubjectObserver._
  15. class ButtonClickObserver extends Observer {
  16. val clicks = new scala.collection.mutable.HashMap[String,Int]() // <3>
  17. def receiveUpdate(button: ObservableButton): Unit = {
  18. val count = clicks.getOrElse(button.label, 0) + 1
  19. clicks.update(button.label, count)
  20. }
  21. }

我们需要定义一个Object继承SubjectObserver, 并在它的内部再定义两个class实现相应的trait。

看下我们如何给S和O赋值:

  1. type S = ObservableButton // <2>
  2. type O = Observer

现在一个观察者模式就完成了。这个例子中我们使用自类型标记来解决使用抽象类型成员时带来的问题。

下面我们再举一个更复杂一点的例子:

  1. trait Persistence { def startPersistence(): Unit } // <1>
  2. trait Midtier { def startMidtier(): Unit }
  3. trait UI { def startUI(): Unit }
  4. trait Database extends Persistence { // <2>
  5. def startPersistence(): Unit = println("Starting Database")
  6. }
  7. trait BizLogic extends Midtier {
  8. def startMidtier(): Unit = println("Starting BizLogic")
  9. }
  10. trait WebUI extends UI {
  11. def startUI(): Unit = println("Starting WebUI")
  12. }
  13. trait App { self: Persistence with Midtier with UI => // <3>
  14. def run() = {
  15. startPersistence()
  16. startMidtier()
  17. startUI()
  18. }
  19. }
  20. object MyApp extends App with Database with BizLogic with WebUI // <4>
  21. MyApp.run

我们定义了一个三层的应用程序,然后在App中调用他们。

在App中我们这样定义自定义类型:

  1. self: Persistence with Midtier with UI =>

意思是App的实例应该是Persistence,Midtier和UI的子类型。

所以在定义App对象的时候就必须要这样定义:

  1. object MyApp extends App with Database with BizLogic with WebUI

使用自类型标记实际上与使用继承和混入等价(除了没有定义self 以外):

  1. trait App extends Persistence with Midtier with UI {
  2. def run = { ... }
  3. }

也有一些特殊情况下,自类型标记的行为不同于继承。但在实践中,这两种方法可以相互替换使用。

事实上,这两种方法表达了不同的意图。刚刚展示的基于继承的实现表明应用程序是Persistence、Midtier 和UI 的一个子类型。与此相反,自类型标记则更加明确地表示其行为的组合是通过混入实现的。

更多教程请参考 flydean的博客

Scala的自定义类型标记的更多相关文章

  1. CodeGen编写自定义表达式标记

    CodeGen编写自定义表达式标记 CodeGen支持开发人员通过编写plug-in modules插件模块来定义自定义表达式标记的能力,以提供与这些标记相关联的逻辑.这种plug-in module ...

  2. 《精通C#》自定义类型转化-扩展方法-匿名类型-指针类型(11.3-11.6)

    1.类型转化在C#中有很多,常用的是int类型转string等,这些都有微软给我们定义好的,我们需要的时候直接调用就是了,这是值类型中的转化,有时候我们还会需要类类型(包括结构struct)的转化,还 ...

  3. 第54讲:Scala中复合类型实战详解

    今天学习了scala的复合类型的内容,让我们通过实战来看看代码: trait Compound_Type1trait Compound_Type2class Compound_Type extends ...

  4. 第53讲:Scala中结构类型实战详解

    今天学习了scala的结构类型,让我们看看代码 class Structural {def open() = print("A class interface opened") } ...

  5. C#简单问题,不简单的原理:不能局部定义自定义类型(不含匿名类型)

    今天在进行代码测试时发现,尝试在一个方法中定义一个委托,注意是定义一个委托,而不是声明一个委托变量,在编写的时候没有报错,VS也能智能提示,但在编译时却报语法不完整,缺少方括号,但实际查询并没有缺少, ...

  6. Struts2框架的自定义类型转换器

    前言:对于java的基本数据类型及一些系统类(如Date类.集合类),Struts2提供了内置类型转换功能,但是也有一定的限制.所以就演示出自定义类型转换器 一.应用于局部类型转换器 eg.用户登录出 ...

  7. sruts2 自定义类型转换器

    1.1.1    Struts2中自定义类型转换器:(了解) 类型转换的过程是双向的过程: JSP---->Action参数提交:String---Date. Action---->JSP ...

  8. 一个关于自定义类型作为HashMap的key的问题

    在之前的项目需要用到以自定义类型作为HashMap的key,遇到一个问题:如果修改了已经存储在HashMap中的实例,会发生什么情况呢?用一段代码来试验: import java.util.HashM ...

  9. Scala 深入浅出实战经典 第54讲:Scala中复合类型实战详解

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

随机推荐

  1. js中使用Timer来计时程序执行时 - [javascript] - [开发]

    在我们开发过程中,我们也在不断的学习,以及优化自己的代码质量. 我们时常需要一个计时器,来对代码某段或者某些段执行进行计时,以评估代码运行质量,考虑是否优化. 以及优化后的直观对比. JavaScri ...

  2. 逍遥云天 H5外部浏览器直接调起微信——通过url协议 weixin:// 判断是否安装微信及启动微信

    h5分享到微信,h5使用微信支付这些功能,都需要先判断是否安装微信客户端,如果已安装就启动微信,如果没有安装微信,就提示用户前去安装. 我们可以通过访问微信提供的URL协议(weixin://)来实现 ...

  3. 转载:Docker源码分析(一):Docker架构

    原文地址: http://www.infoq.com/cn/articles/docker-source-code-analysis-part1  作者:孙宏亮 1 背景 1.1 Docker简介 D ...

  4. php--一些有用的Laravel辅助函数

    str_start()/str_finish() 将指定值添加到字符串的开头/结尾(当不是以该值开头/结尾时) blank() 判断给定的值是否为「空」 collect() 根据给定的数组创建一个集合 ...

  5. C++ namespace 命名空间

    namespace即"命名空间",也称"名称空间" 那么这个 "名称空间" 是干啥的呢? 我们都知道,C/C++中的作用域可以由一个符号 { ...

  6. JAVA中的==和equals()的区别

    一.先来说说Java的基本数据类型和引用类型 八大基本数据类型:Byte,short,int,long,double,folat,boolean,char,其中占一个字节的是byte,short和ch ...

  7. 单线程IP扫描解析

    扫描代码: private void Button_Click(object sender, RoutedEventArgs e) { a5.Items.Clear(); string str = t ...

  8. 29.2 Iterator 迭代器ConcurrentModificationException:并发修改异常处理

    /** Iterator:迭代器* * 需求:判断集合中是否包含元素java,如果有则添加元素android * Exception in thread "main" java.u ...

  9. 本地项目推送到Github

    1.在github上repositories新建一个git项目工程 2.使用git,把刚建好的项目clone到本地 3.把本地项目中的文件全部移动到下载下来的git项目中,以下是我本地项目中的文件 4 ...

  10. AJ学IOS 之CoreLocation地理编码小Demo输入城市得到经纬度

    AJ分享,必须精品 一:效果 输入地名,可以得到相应的经纬度,知识为了学习写的小Demo 二:实现步骤 一 :首先获取用户输入的位置. 二 :创建地理编码对象. 三 :利用地理编码对象编码,根据传入的 ...