文章目录
特质
概述:
有些时候,我们会遇到一些特定的需求,即:在不影响当前继承体系的情况下,对某些类(或者某些对象)的功能进行加强,例如:有猴子类和大象类,它们都有姓名,年龄,以及吃的功能,但是部分猴子经过马戏训练之后,学会了骑独轮车,那么骑独轮车这个功能不能定义到父类(动物类)或者猴子类当中,而实应该定义到特质当中。而Scala中的特质,要用关键字 trait 修饰。
注意:Scala中的特质和Java中的接口很像
特点:
- 特质可以提高代码的复用性
- 特质可以提高代码的扩展性和可维护性。
- 类与特质之间的继承关系,只不过是类与类之间只支持单继承,但是类与特质之间,既可以支持单继承,也可以支持多继承。
- Scala的特质中可以有普通字段,抽象字段,普通方法,抽象方法。
注意:
- 1.如果特质中只有抽象内容,这样的特质叫:瘦特质。
- 2.如果特质当中既有抽象内容,又有具体内容,这样的特质叫:复接口。
语法:
定义特质:
trait 特质名称{
// 普通字段
// 抽象字段
// 普通方法
// 抽象方法
}
class 类 extends 特质1 with 特质2 {
// 重写抽象字段
// 重写抽象方法
}
注意:
- Scala中不管是特质还是类,继承关系用的是 extends 关键字。
- 如果要继承多个特质,则特质名之间使用 with 关键字隔开。
类继承单个特质
代码示例:
- 创建一个Logger特质,添加 log(msg:String) 方法
- 创建一个ConsoleLogger类,继承Logger特质,实现log方法,打印消息
- 添加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:类继承单个特质
类继承多个特质
代码示例:
- 创建一个MessageSender特质,添加 send(msg:String) 方法
- 创建一个MessageReceiver特质,添加 receive() 方法
- 创建一个MessageWorker类, 继承这两个特质, 重写上述的两个方法
- 在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
代码演示:
- 创建一个Logger特质,添加 log(msg:String) 方法
- 创建一个Warning特质, 添加 warn(msg:String) 方法
- 创建一个单例对象ConsoleLogger,继承Logger和Warning特质, 重写特质中的抽象方法
- 编写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("我是一条警告日志信息")
}
}
控制台日志信息:我是一条普通的日志信息
控制台警告信息:我是一条警告日志信息
演示特质中的成员
代码示例:
- 定义一个特质Hero, 添加具体字段name(姓名), 抽象字段arms(武器), 具体方法eat(), 抽象方法toWar()
- 定义一个类Generals, 继承Hero特质, 重写其中所有的抽象成员.
- 在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 特质
代码实例:
- 创建Logger特质, 添加log(msg:String)方法
- 创建一个User类, 该类和Logger特质之间无任何关系.
- 在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 {
//需要使用哪个方法, 重写哪个方法即可.
}
代码示例:
- 定义特质PlayLOL, 添加6个抽象方法, 分别为: top(), mid(), adc(), support(), jungle(), schoolchild()
解释: top: 上单, mid: 中单, adc: 下路, support: 辅助, jungle: 打野, schoolchild: 小学生 - 定义抽象类Player, 继承PlayLOL特质, 重写特质中所有的抽象方法, 方法体都为空.
- 定义普通类GreenHand, 继承Player, 重写support()和schoolchild()方法.
- 定义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 {
//重写抽象方法
}
注意:抽象方法的个数要根据具体的需求来定,并不一定只有一个,也可以是多个。
代码实例:
- 定义一个模板类Template, 添加code()和getRuntime()方法, 用来获取某些代码的执行时间.
- 定义类ForDemo继承Template, 然后重写code()方法, 用来计算打印10000次"Hello,Scala!"的执行时间.
- 定义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代码, 实现一个模拟支付过程的调用链.)
解释:
我们如果要开发一个支付功能,往往需要执行一系列的验证才能完成支付。例如:进行支付签名验证;数据合法校验;等等。如果将来因为第三方接口支付的调整,需要增加更多的校验规则,此时如何不修改之前的校验码,来实现扩展呢??
这就需要用到:职责链设计模式了。
图解:
步骤:
- 定义一个Handler特质, 添加具体的handle(data:String)方法,表示处理数据(具体的支付逻辑) 2. 定义一个DataValidHandler特质,继承Handler特质.
重写handle()方法,打印"验证数据", 然后调用父特质的handle()方法 - 定义一个SignatureValidHandler特质,继承Handler特质.
重写handle()方法, 打印"检查签名", 然后调用父特质的handle()方法 - 创建一个Payment类, 继承DataValidHandler特质和SignatureValidHandler特质
定义pay(data:String)方法, 打印"用户发起支付请求", 然后调用父特质的handle()方法 - 添加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的构造顺序,步骤如下:
- 创建Logger特质,在构造器中打印"执行Logger构造器!"
- 创建MyLogger特质,继承自Logger特质,,在构造器中打印"执行MyLogger构造器!"
- 创建TimeLogger特质,继承自Logger特质,在构造器中打印"执行TimeLogger构造器!"
- 创建Person类,在构造器中打印"执行Person构造器!"
- 创建Student类,继承Person类及MyLogger, TimeLogge特质,在构造器中打印"执行Student构造器!"
- 添加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
}
代码实例:
- 定义Message类. 添加printMsg()方法, 打印"学好Scala, 走到哪里都不怕!"
- 创建Logger特质,继承Message类.
- 定义ConsoleLogger类, 继承Logger特质.
- 在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!