Scala特质讲解【特质继承形式、对象混入特质、特质适配器模式,模板方法模式,职责链模式、trait的构造机制、特质继承类】


特质

概述:

有些时候,我们会遇到一些特定的需求,即:在不影响当前继承体系的情况下,对某些类(或者某些对象)的功能进行加强,例如:有猴子类和大象类,它们都有姓名,年龄,以及吃的功能,但是部分猴子经过马戏训练之后,学会了骑独轮车,那么骑独轮车这个功能不能定义到父类(动物类)或者猴子类当中,而实应该定义到特质当中。而Scala中的特质,要用关键字 trait 修饰

注意:Scala中的特质和Java中的接口很像


特点:

  • 特质可以提高代码的复用性
  • 特质可以提高代码的扩展性和可维护性。
  • 类与特质之间的继承关系,只不过是类与类之间只支持单继承,但是类与特质之间,既可以支持单继承,也可以支持多继承
  • Scala的特质中可以有普通字段,抽象字段,普通方法,抽象方法。

注意:

  • 1.如果特质中只有抽象内容,这样的特质叫:瘦特质
  • 2.如果特质当中既有抽象内容,又有具体内容,这样的特质叫:复接口

语法:

定义特质:

trait 特质名称{
	// 普通字段 
	// 抽象字段 
	
	// 普通方法 
	// 抽象方法
}

class 类 extends 特质1 with 特质2 { 
// 重写抽象字段 
// 重写抽象方法 
}

注意:

  • Scala中不管是特质还是类,继承关系用的是 extends 关键字。
  • 如果要继承多个特质,则特质名之间使用 with 关键字隔开。

类继承单个特质

代码示例:

  1. 创建一个Logger特质,添加 log(msg:String) 方法
  2. 创建一个ConsoleLogger类,继承Logger特质,实现log方法,打印消息
  3. 添加main方法,创建ConsoleLogger对象,调用log方法
object 类继承单个特质 {
    
    
  //1. 定义一个特质.
  trait Logger {
    
    
    def log(msg:String) //抽象方法
  }

  //2.定义一个类继承特质
  class ConsoleLogger extends Logger {
    
    
    override def log(msg: String): Unit = println(msg)
  }

  def main(args: Array[String]): Unit = {
    
    
    //3,调用类中的方法
    val cl = new ConsoleLogger
    cl.log("trait:类继承单个特质")
  }

}
trait:类继承单个特质

类继承多个特质

代码示例:

  1. 创建一个MessageSender特质,添加 send(msg:String) 方法
  2. 创建一个MessageReceiver特质,添加 receive() 方法
  3. 创建一个MessageWorker类, 继承这两个特质, 重写上述的两个方法
  4. 在main中测试,分别调用send方法、receive方法
object 类继承多个特质 {
    
    
  //1. 定义一个特质: MessageSender, 表示发送信息。
  trait MessageSender {
    
    
    def send(msg:String)
  }
  //2. 定义一个特质: MessageReceiver, 表示接收信息。
  trait MessageReceiver {
    
    
    def receive()
  }
  //3. 定义一个类MessageWorker, 继承两个特质.
  class MessageWorker extends MessageSender with MessageReceiver {
    
    
    override def send(msg: String): Unit = println("发送消息:" + msg)

    override def receive(): Unit = println("消息已收到,我很好,谢谢!!!")
  }

  //main方法, 作为程序的主入口
  def main(args: Array[String]): Unit = {
    
    
    //4.调用类中的方法
    val mw = new MessageWorker
    mw.send("Hello,你好啊!!!")
    mw.receive()
  }

}
发送消息:Hello,你好啊!!!
消息已收到,我很好,谢谢!!!

object继承特质trait

代码演示:

  1. 创建一个Logger特质,添加 log(msg:String) 方法
  2. 创建一个Warning特质, 添加 warn(msg:String) 方法
  3. 创建一个单例对象ConsoleLogger,继承Logger和Warning特质, 重写特质中的抽象方法
  4. 编写main方法,调用单例对象ConsoleLogger的log和warn方法
object Object继承特质trait {
    
    

  //1. 定义一个特质Logger, 添加log(msg:String)方法.
  trait Logger {
    
    
    def log(msg:String)
  }

  //2. 定义一个特质Warning, 添加warn(msg:String)方法.
  trait Warning {
    
    
    def warn(msg:String)
  }

  //3. 定义单例对象ConsoleLogger, 继承上述两个特质, 并重写两个方法.
  object ConsoleLogger extends Logger with Warning {
    
    
    override def log(msg: String): Unit = println("控制台日志信息:" + msg)

    override def warn(msg: String): Unit = println("控制台警告信息:" + msg)
  }

  //main方法, 作为程序的入口
  def main(args: Array[String]): Unit = {
    
    
    //4. 调用ConsoleLogger单例对象中的两个方法.
    ConsoleLogger.log("我是一条普通的日志信息")
    ConsoleLogger.warn("我是一条警告日志信息")
  }

}
控制台日志信息:我是一条普通的日志信息
控制台警告信息:我是一条警告日志信息

演示特质中的成员

代码示例:

  1. 定义一个特质Hero, 添加具体字段name(姓名), 抽象字段arms(武器), 具体方法eat(), 抽象方法toWar()
  2. 定义一个类Generals, 继承Hero特质, 重写其中所有的抽象成员.
  3. 在main方法中, 创建Generals类的对象, 调用其中的成员.
object 演示特质中的成员 {
    
    
  //1. 定义一个特质Hero
  trait Hero {
    
    
    var name = ""   //具体字段
    var arms:String   //抽象字段

    //具体方法
    def eat() = println("")

    //抽象方法
    def toWar():Unit
  }

  //2. 定义一个类Generals, 继承Hero特质, 重写其中所有的抽象成员.
  class Generals extends Hero {
    
    
    //重写父特质中的抽象字段
    override var arms: String = ""

    //重写父特质中的抽象方法
    override def toWar(): Unit = println(s"${name}带着${arms},上阵杀敌!")
  }

  //main方法, 作为程序的入口
  def main(args: Array[String]): Unit = {
    
    
    //3. 创建Generals类的对象
    val gy = new Generals

    //4. 测试Generals类中的内容
    //给成员变量赋值
    gy.name = "关羽"
    gy.arms = "青龙偃月刀"
    //打印成员变量值
    println(gy.name, gy.arms)
    //调用成员方法
    gy.eat()
    gy.toWar()
  }

}
(关羽,青龙偃月刀)

关羽带着青龙偃月刀,上阵杀敌!


对象混入特质

有些时候,我们希望在不改变类继承的情况下,对对象的功能进行临时增强或者扩展,这个时候就可以考虑使用对象混入技术了。所谓的对象混入,指的就是:在Scala中,类和特质之间无任何的继承关系,但是通过特定的关键字,却可以让类对象具有特质中的成员

语法:

val/var 对象名 = new 类 with 特质

代码实例:

  1. 创建Logger特质, 添加log(msg:String)方法
  2. 创建一个User类, 该类和Logger特质之间无任何关系.
  3. 在main方法中测试, 通过对象混入技术让User类的对象具有Logger特质的log()方法.
object 对象混入特质 {
    
    

  //1. 创建Logger特质, 添加log(msg:String)方法
  trait Logger {
    
    
    def log(msg:String) = println(msg)
  }

  //2. 创建一个User类, 该类和Logger特质之间无任务关系.
  class User

  //main方法, 作为程序的入口
  def main(args: Array[String]): Unit = {
    
    
    //3. 在main方法中测试, 通过对象混入技术让User类的对象具有Logger特质的log()方法.
    val cl = new User with Logger //对象混入
    cl.log("我是User类的对象,我可以调用Logger特质中的log方法了")
  }

}
我是User类的对象,我可以调用Logger特质中的log方法了


使用trait实现适配器模式

设计模式

概述:
设计模式(Design Pattern)是前辈们对代码开发总结的经验,是解决特定问题的一系列套路。它并不是语法规定,而是一套来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。

设计模式有如下分类:

1. 创建型
指的是: 需要创建对象的. 常用的模式有: 单例模式, 工厂方法模式
2. 结构型
指的是: 类,特质之间的关系架构. 常用的模式有: 适配器模式, 装饰模式
3. 行为型
指的是: 类(或者特质)能够做什么. 常用的模式有:模板方法模式, 职责链模式

适配器模式
当特质中有很多抽象方法时,而我们只需要用到其中的某一个或者某几个方法时,不得不将该特质中的所有抽象方法给重写了,这样做很麻烦,针对这样的情况,我们可以定义一个抽象类去继承该特质,重写特质中所有的抽象方法,方法体为空。这时候,我们需要使用哪个方法,只需要定义类继承抽象类,重写指定方法即可。这个抽象类就叫做:适配器类。这种设计模式(设计思想)就叫做:适配器模式

结构如下:

trait 特质A{ 
//抽象方法1 
//抽象方法2 
//抽象方法3 //... 
}
abstract class 类B extends A{ //适配器类 
//重写抽象方法1, 方法体为空 
//重写抽象方法2, 方法体为空 
//重写抽象方法3, 方法体为空 //... 
}

class 自定义类C extends 类B { 
//需要使用哪个方法, 重写哪个方法即可. 
}

代码示例:

  1. 定义特质PlayLOL, 添加6个抽象方法, 分别为: top(), mid(), adc(), support(), jungle(), schoolchild()
    解释: top: 上单, mid: 中单, adc: 下路, support: 辅助, jungle: 打野, schoolchild: 小学生
  2. 定义抽象类Player, 继承PlayLOL特质, 重写特质中所有的抽象方法, 方法体都为空.
  3. 定义普通类GreenHand, 继承Player, 重写support()和schoolchild()方法.
  4. 定义main方法, 在其中创建GreenHand类的对象, 并调用其方法进行测试
object 适配器设计模式 {
    
    

  //1. 定义特质PlayLOL, 添加6个抽象方法, 分别为: top(), mid(), adc(), support(), jungle(), schoolchild()
  trait PlayLOL {
    
    
    def top()   //上单
    def mid()   //中单
    def adc()   //下路
    def support()   //辅助
    def jungle()    //打野
    def schoolchild()   //小学生
  }

  //2. 定义抽象类Player, 继承PlayLOL特质, 重写特质中所有的抽象方法, 方法体都为空.
  //Player类充当的角色就是:适配器类
  class player extends PlayLOL {
    
    
    override def top(): Unit = {
    
    }
    override def mid(): Unit = {
    
    }
    override def adc(): Unit = {
    
    }
    override def support(): Unit = {
    
    }
    override def jungle(): Unit = {
    
    }
    override def schoolchild(): Unit = {
    
    }
  }

  //3. 定义普通类GreenHand, 继承Player, 重写抽象方法。
  class GreenHand extends PlayLOL{
    
    
    override def top(): Unit = println("上单盖伦!")
    override def mid(): Unit = println("中单亚索!")
    override def adc(): Unit = println("adc寒冰!")
    override def support(): Unit = println("辅助布伦!")
    override def jungle(): Unit = println("打野剑圣!")
    override def schoolchild(): Unit = println("我是小学生, 你骂我, 我就挂机!")
  }

  //4. 定义main方法, 在其中创建GreenHand类的对象, 并调用其方法进行测试.
  def main(args: Array[String]): Unit = {
    
    
    //创建GreenHand类的对象
    val gh = new GreenHand
    //调用GreenHand类中的方法
    gh.support()
    gh.schoolchild()
  }

}
辅助布伦!
我是小学生, 你骂我, 我就挂机!


使用trait实现模板方法模式

在现实生活中, 我们会遇到论文模板, 简历模板, 包括PPT中的一些模板等, 而在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的
具体实现还未知,或者说某些步骤的实现与具体的环境相关。

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人
而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。这就要用到模板方法设计模式了.

概述:
在Scala中,我们可以先定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类当中去,使得子类不改变算法结构的情况下重定义该算法的某些特定步骤,这就是:模板方法设计。

优点:

  • 1.扩展性更强。
    • 父类中封装了公共的部分,而可变的部分交给子类来实现。
  • 2.符合开闭原则。
    • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能。

缺点:

  • 1.类的个数增加,导致系统更加庞大,设计也更加抽象。
    • 因为要对每个不同的实现都需要定义一个子类。
  • 2.提高了代码阅读的难度。
    • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这是一种反向的控制结构。

格式如下:

class A { //父类, 封装的是公共部分 
	def 方法名(参数列表) = { //具体方法, 在这里也叫: 模板方法 
		//步骤1, 已知. 
		//步骤2, 未知, 调用抽象方法 
		//步骤3, 已知. 
		//步骤n... 
	}
//抽象方法 
}
class B extends A { 
	//重写抽象方法 
}

注意:抽象方法的个数要根据具体的需求来定,并不一定只有一个,也可以是多个。

代码实例:

  1. 定义一个模板类Template, 添加code()和getRuntime()方法, 用来获取某些代码的执行时间.
  2. 定义类ForDemo继承Template, 然后重写code()方法, 用来计算打印10000次"Hello,Scala!"的执行时间.
  3. 定义main方法, 用来测试代码的具体执行时间.
object 模板方法设计模式 {
    
    

  //1. 定义一个模板类Template, 添加code()和getRuntime()方法, 用来获取某些代码的执行时间.
  abstract class Template {
    
    
    //定义code()方法, 用来记录所有要执行的代码
    def code()

    //定义模板方法, 用来获取某些代码的执行时间.
    def getRuntime() = {
    
    
      //获取当前时间毫秒值
      val start = System.currentTimeMillis()
      //具体要执行的代码
      code()
      //获取当前时间毫秒值
      val end = System.currentTimeMillis()
      //返回指定代码的执行时间
      end - start

    }
  }

  //2. 定义类ForDemo继承Template, 然后重写getRuntime()方法, 用来计算打印10000 次"Hello,Scala!"的执行时间.
  class ForDemo extends Template {
    
    
    override def code(): Unit = for(i <- 1 to 10000) println("Hello Scala!")
  }

  def main(args: Array[String]): Unit = {
    
    
    //3. 测试打印10000次"Hello, Scala!"的执行时间
    println(new ForDemo().getRuntime())
  }

}


使用trait实现职责链模式

概述:
多个trait中出现了同一个方法,而且该方法最后都调用了super.该方法名(),当类继承了这多个trait后,就可以依次调用多个trait中的此同一个方法了,这就形成一个调用链。

执行顺序为:

  • 1.按照从右到左的顺序依次执行。
    • 即首先从最右边的trait方法开始执行,然后依次往左执行对应的trait中的方法。
  • 2.当所有的子特质的该方法执行完毕之后,最后会执行父特质中的此方法。
    • 注意:在Scala中,一个类继承多个特质的情况叫叠加特质

语法格式如下:

trait A { //父特质 
	def show() //假设方法名叫: show 
}
trait B extends A { //子特质, 根据需求可以定义多个. 
	override def show() = { 
		//具体的代码逻辑. 
		super.show() 
	} 
}
trait C extends A { 
	override def show() = { 
		//具体的代码逻辑. 
		super.show() 
	} 
}
class D extends B with C { //具体的类, 用来演示: 叠加特质. 
	def 方法名() = { //这里可以是该类自己的方法, 不一定非的是show()方法. 
		//具体的代码逻辑. 
		super.show() //这里就构成了: 调用链. 
	} 
}
/* 
执行顺序为: 
	1. 先执行类D中的自己的方法. 
	2. 再执行特质C中的show()方法. 
	3. 再执行特质B中的show()方法. 
	4. 最后执行特质A中的show()方法. 
*/

代码演示:(通过Scala代码, 实现一个模拟支付过程的调用链.)

解释:
我们如果要开发一个支付功能,往往需要执行一系列的验证才能完成支付。例如:进行支付签名验证;数据合法校验;等等。如果将来因为第三方接口支付的调整,需要增加更多的校验规则,此时如何不修改之前的校验码,来实现扩展呢??

这就需要用到:职责链设计模式了。

图解:
在这里插入图片描述
步骤:

  1. 定义一个Handler特质, 添加具体的handle(data:String)方法,表示处理数据(具体的支付逻辑) 2. 定义一个DataValidHandler特质,继承Handler特质.
    重写handle()方法,打印"验证数据", 然后调用父特质的handle()方法
  2. 定义一个SignatureValidHandler特质,继承Handler特质.
    重写handle()方法, 打印"检查签名", 然后调用父特质的handle()方法
  3. 创建一个Payment类, 继承DataValidHandler特质和SignatureValidHandler特质
    定义pay(data:String)方法, 打印"用户发起支付请求", 然后调用父特质的handle()方法
  4. 添加main方法, 创建Payment对象实例, 然后调用pay()方法.
object 职责链模设计式 {
    
    
  //1. 定义一个父特质 Handler, 表示处理数据(具体的支付逻辑)
  trait Handler {
    
    
    def handle(data:String) = {
    
    
      println("具体处理数据的代码(例如:转账逻辑)")
      println(data)
    }
  }
  //2. 定义一个子特质 DataValidHandler, 表示 校验数据.
  trait DataValidHandler extends Handler{
    
    
    override def handle(data: String) = {
    
    
      println("校验数据...")
      super.handle(data)
    }
  }
  //3. 定义一个子特质 SignatureValidHandler, 表示 校验签名.
  trait SignatureValidHandler extends Handler {
    
    
    override def handle(data: String) = {
    
    
      println("校验签名...")
      super.handle(data)
    }
  }
  //4. 定义一个类Payment, 表示: 用户发起的支付请求.
  class Payment extends DataValidHandler with SignatureValidHandler {
    
    
    def pay(data:String) = {
    
    
      println("用户发起支付请求...")
      super.handle(data)
    }
  }

  def main(args: Array[String]): Unit = {
    
    
    //5. 创建Payment类的对象, 模拟: 调用链.
    val pm = new Payment
    pm.pay("shuyv给xin转账10000¥")
  }
  // 程序运行输出如下:
  // 用户发起支付请求...
  // 校验签名...
  // 校验数据...
  // 具体处理数据的代码(例如: 转账逻辑)
  // 苏明玉给苏大强转账10000元

}
用户发起支付请求...
校验签名...
校验数据...
具体处理数据的代码(例如:转账逻辑)
shuyv给xin转账10000


trait的构造机制

概述:
如果遇到一个类继承了某个父类而且继承了多个父特质的情况,那该类(子类),该类的父类,以及该类的父特质之间是如何构造的呢?

要想解决这个问题,就要用到 trait的构造机制 了。

构造机制规则:

  • 每一个特质都是一个无参数的构造器。
    • 也就是说:trait也有构造代码,但和类不一样,特质不能有构造器参数。
  • 遇到一个类继承另一个类,以及多个trait的情况,当创建该类的实例时,它的构造器执行顺序如下:
    • 1.按照父类的构造器。
    • 2.按照从左到右的顺序,一次执行trait的构造器。
    • 3.如果trait有父trait,则先执行父trait的构造器。
    • 4.如果多个trait有同样的父trait,则父trait的构造器只初始化一次
    • 5.执行子类构造器。

代码实例:

定义一个父类及多个特质,然后用一个类去继承它们.
创建子类对象, 并测试trait的构造顺序,步骤如下:

  1. 创建Logger特质,在构造器中打印"执行Logger构造器!"
  2. 创建MyLogger特质,继承自Logger特质,,在构造器中打印"执行MyLogger构造器!"
  3. 创建TimeLogger特质,继承自Logger特质,在构造器中打印"执行TimeLogger构造器!"
  4. 创建Person类,在构造器中打印"执行Person构造器!"
  5. 创建Student类,继承Person类及MyLogger, TimeLogge特质,在构造器中打印"执行Student构造器!"
  6. 添加main方法,创建Student类的对象,观察输出。
object trait的构造机制 {
    
    
  //1. 创建Logger父特质
  trait Logger {
    
    
    println("执行Logger构造器")
  }
  //2. 创建MyLogger子特质, 继承Logger特质
    trait MyLogger extends Logger {
    
    
    println("执行MyLogger构造器")
  }
  //3. 创建TimeLogger子特质, 继承Logger特质.
  trait TimeLogger extends Logger {
    
    
    println("执行TimeLogger构造器")
  }
  //4. 创建父类Person
  class Person {
    
    
    println("执行Person构造器")
  }
  //5. 创建子类Student, 继承Person类及TimeLogger和MyLogger特质.
  class Student extends Person with TimeLogger with MyLogger {
    
    
    println("执行Student构造器")
  }
  //main方法, 程序的入口.
  def main(args: Array[String]): Unit = {
    
    
    //6. 创建Student类的对象,观察输出。
    new Student
  }
}
执行Person构造器
执行Logger构造器
执行TimeLogger构造器
执行MyLogger构造器
执行Student构造器


trait继承class

概述:
在Scala中,trait也可以继承class。特质会将class中的成员都继承下来。

语法格式:

class 类A { //类A 
	//成员变量 
	//成员方法 
}

trait B extends A { //特质B 
}

代码实例:

  1. 定义Message类. 添加printMsg()方法, 打印"学好Scala, 走到哪里都不怕!"
  2. 创建Logger特质,继承Message类.
  3. 定义ConsoleLogger类, 继承Logger特质.
  4. 在main方法中, 创建ConsoleLogger类的对象, 并调用printMsg()方法.
object 特质继承类 {
    
    
  //1. 定义Message类. 添加printMsg()方法, 打印"测试数据..."
  class Message {
    
    
    def printMsg() = println("学好Scala,干好Spark!")
  }
  //2. 创建Logger特质,继承Message类.
  trait Logger extends Message
  //3. 定义ConsoleLogger类, 继承Logger特质.
  class ConsoleLogger extends Logger

  def main(args: Array[String]): Unit = {
    
    
    //4. 创建ConsoleLogger类的对象, 并调用printMsg()方法.
    val cl = new ConsoleLogger
    cl.printMsg()
  }

}
学好Scala,干好Spark!

猜你喜欢

转载自blog.csdn.net/shuyv/article/details/119039243