[读书日志]从零开始学习Chisel 第六篇:Scala面向对象编程——特质(敏捷硬 ...

守听  金牌会员 | 2025-1-11 22:28:05 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 873|帖子 873|积分 2619

3.4特质

3.4.1什么是特质

特质使用trait开头,它与单例对象很像,两者都不能有输入参数,但单例对象是详细的,特质是抽象的。两者都不能用new实例化,类,单例对象,特质三者内部都可以包罗字段和方法,以及其他类,单例对象,特质的界说。
特质可以被其他类,单例对象和特质“混入”。混入在超类调用上采用线性化机制,在其他方面其实和继承是一样的。某个类混入一个特质后,会包罗特质的所有公有成员,可以通过override来重写特质的成员。Scala只允许继承自一个类,但是对特质的混入数目没有限定,因此用来替代多重继承的语法。要混入一个特质,可以使用关键字extend,已被占用后可以使用with混入其他特质。
  1. scala> class A {
  2.      | val a = "Class A"
  3.      | }
  4. // defined class A
  5. scala> trait B {
  6.      | val b = "Trait B"
  7.      | }
  8. // defined trait B
  9. scala> trait C {
  10.      | val c = "Trait C"
  11.      | }
  12. // defined trait C
  13. scala> object D extends A with B with C
  14. // defined object D
  15. scala> D.a
  16. val res7: String = Class A
  17. scala> D.b
  18. val res8: String = Trait B
  19. scala> D.c
  20. val res9: String = Trait C
复制代码
特质也界说了一个类型,可以指向混入该特质的对象。
  1. scala> trait A
  2. // defined trait A
  3. scala> class B extends A
  4. // defined class B
  5. scala> val x: A = new B
  6. val x: A = B@39159b14
复制代码
3.4.2特质的条理

特质也可以继承自其他类,或混入任意多个特质,对混入有一个限定条件,即要混入该特质的类/单例对象/特质,它的超类必须是待混入特质的超类,或是待混入特质的超类的子类。换句话说,一个特质假如想要“继承”(注意,这里的“继承”现实上是“混入”,只是为了理解特质用来替代多重继承的特质。)另一个特质,那么它只能继承比它层级更低的特质。
  1. scala> class A
  2. // defined class A
  3. scala> class B extends A
  4. // defined class B
  5. scala> class C
  6. // defined class C
  7. scala> trait D extends A
  8. // defined trait D
  9. scala> trait E extends B
  10. // defined trait E
  11. scala> class Test1 extends D
  12. // defined class Test1
  13. scala> class Test2 extends A with D
  14. // defined class Test2
  15. scala> class Test3 extends B with D
  16. // defined class Test3
  17. scala> class Test4 extends C with D
  18. -- Error: --------------------------------------------------------------------------------------------------------------
  19. 1 |class Test4 extends C with D
  20.   |                           ^
  21.   |                           illegal trait inheritance: superclass C does not derive from trait
  22. D's superclass A
  23. 1 error found
  24. scala> class Test5 extends A with E
  25. -- Error: --------------------------------------------------------------------------------------------------------------
  26. 1 |class Test5 extends A with E
  27.   |                           ^
  28.   |                           illegal trait inheritance: superclass A does not derive from trait
  29. E's superclass B
  30. 1 error found
复制代码
画图表示如下:

Test4继承自classC,它混入的特质是traitD,该特质的超类是classA,它不是classC的超类或超类的子类,因此无法混入;Test5继承自classA,它混入的traitE继承自classB,但classB是classA的子类,因此不能混入。假如把它们换一下顺序,使Test6继承classB而混入一个超类是classA的特质traitD,那么则可以运行。这个例子说明,一个类或特质的超类是哪个,是由extends关键字决定的。
  1. scala> class Test6 extends B with D
  2. // defined class Test6
复制代码
3.4.3混入特质的简便方法

假如要快速构建一个混入某些特质的实例,可以使用这个语法:
new Trait1 with Trait2 ... {definition}
这句话界说了一个匿名类,匿名类混入了这些特质。还可以在最前面加上一个想要继承的超类:
new SuperClass with Trait1 with Trait2 ... {definition}
3.4.4特质的线性化叠加计算

多重继承中一个很显着的题目是当子类调用超类的方法时,若多个超类都有该方法的差别实现,那么需要额外的语法确定详细调用哪个版本。Scala的特质采取一种线性化的规则来调用特质中的方法,在特质中,super调用是动态绑定的,按特质本身的界说,无法确定super调用的详细行为,直到特质混入某个类或别的特质,有了详细的超类方法,才气确定super的行为,这是实现线性化的基础。
若要通过混入特质来实现某个方法的线性叠加,有以下几个要求:

  • 需要在特质中界说同名同参的方法,并加关键字组合abstract override,这个关键词不是重写,只能用于特质中;
  • 该特质必须混入某个拥有该方法的详细界说的类中,这个类界说了该方法的最终行为;
  • 需要混入特质进行线性化计算的类,界说时不能立即混入特质,这样做会让编译器以为这个类是在重写末了那个特质的方法。应该先界说这个类的子类来混入特质,然后构造子类的对象,或者直接用3.4.3所述方法快速构造子类对象;
  • 特质对该方法的界说必须出现super.方法名(参数)
  • 方法的实行顺序遵循线性化计算公式,出发点是公式从左往右第一个特质,外部传入的参数由出发点接收,出发点的super.方法名(参数)会调用出发点右边第一个特质的同名方法,并把出发点的计算结果作为参数转达,以此类推。最后结果会回到最左边的类本身。
  • 这个类现实上直接或间接重写或实现了基类的方法,假如界说中也出现了super.方法名(参数),则会调用它的上一层超类的实现版本。假如这个类没有重写,那就要有继承自超类的实现。
线性化计算公式:

  • 最左边是类本身;
  • 在类的右边写下界说时最后混入的那个特质,并接着往右按继承顺序写下该特质的所有超类和超特质;
  • 继续往右写下倒数第二个混入的特质,以及其超类和超特质,直到写完所有特质;
  • 所有重复项值生存最右边的那个,并在最右边加上AnyRef和Any
如今我们编写这样的一个test.scala文件:
  1. abstract class A {
  2.   def m(s: String) : String
  3. }
  4. class X extends A {
  5.   def m(s: String) = "X ->" + s
  6. }
  7. trait B extends A {
  8.   abstract override def m(s: String) = super.m("B ->" + s)
  9. }
  10. trait C extends A {
  11.   abstract override def m(s: String) = super.m("C ->" + s)
  12. }
  13. trait D extends A {
  14.   abstract override def m(s: String) = super.m("D ->" + s)
  15. }
  16. trait E extends C {
  17.   abstract override def m(s: String) = super.m("E ->" + s)
  18. }
  19. trait F extends C {
  20.   abstract override def m(s: String) = super.m("F ->" + s)
  21. }
  22. class G extends X {
  23.   override def m(s: String) = "G ->" + s
  24. }
  25. val x = new G with D with E with F with B
  26. @main def test() = println(x.m("End"))
复制代码
最终,需要混入特质进行线性化运算的类G在界说时没有立即混入特质,只是继承了classX,且通过new G with D with E with F with B构建了G的匿名子类的对象。类A是一个抽象类,类X实现了抽象方法m,类G重写了X的m,其余特质也用abstract override重写了m,这保证m最后会回到G。A的m返回类型“String”是必须的。
根据线性化计算公式,我们一步步进行计算。注意类X也就是这个类G继承的类不到场运算,最左边是类本身:G ->,在类的右边写下界说时最后混入的特质,然后往右按照继承顺序写下该特质所有的超类和超特质,全部写完后再写第二个混入的类,以此类推。因此第二个写下的是最后混入的特质B和它的超类:G -> B -> A接着写类F。以此类推,我们写出:
G -> B -> A -> F -> C -> A -> E -> C -> A -> D -> A
之后我们删除其中重复的部分:
G -> B -> F -> E -> C -> D -> A
之后补上AnyRef和Any:
G -> B -> F -> E -> C -> D -> A -> AnyRef -> Any
出发点是B,传入“End”,B输出“B -> End”,之后再传给F,输出“F -> B -> End”,继续向右调用,最后到A时没有操作可以实行,转回G的m,最终得到运行结果是:
G ->D ->C ->E ->F ->B ->End
假如G的m也有super或没有重写,那么会调用X的m,导致最后最左边多一个X,假如在界说G时立即混入特质,则相当于普通的方法重写,假如上一层超类是抽象类,立即引入会报错。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

守听

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表