文章目录
特质入门
1.概述
有些时候,我们会遇到一些特定的需求,即:在不影响当前继承体系的情况下,对某些类(或者某些对象)的功能进行加强,例如:有猴子类和大象类,它们都有姓名、年龄以及吃的功能,但是部分的猴子经过马戏团的训练后,学会了骑独轮车,那骑独轮车就不能定义到父类(动物类)或者猴子类中,而是定义到特质中,而Scala中的特质,要用关键字trait修饰
2.特点
- 特质可以提高代码的复用性
- 特质可以提高代码的扩展性和可维护性
- 类与特质之间是继承关系,类跟类之间只支持单继承,类与特质之间,既可以单继承,也可以多继承
- Scala的特质中可以有普通字段、抽象字段、普通方法和抽象方法
3.语法
格式:
trait 特质名称 {
//普通字段
//抽象字段
//普通方法
//抽象方法
}
类继承特质:
class 类 extends 特质1 with 特质2 ..{
//重写抽象字段
//重写抽象方法
}
4.类继承多个特质
需求
- 创建一个MessageSender特质,添加
send(msg:String)
方法 - 创建一个MessageReceiver特质,添加
receive()
方法 - 创建一个MessageWorker类, 继承这两个特质, 重写上述的两个方法
- 在main中测试,分别调用send方法、receive方法
代码
//案例: 类继承多个trait
object ClassDemo02 {
Ob'j
}
5.object 继承 trademoit
需求
- 创建一个Logger特质,添加
log(msg:String)
方法 - 创建一个Warning特质, 添加
warn(msg:String)
方法 - 创建一个单例对象ConsoleLogger,继承Logger和Warning特质, 重写特质中的抽象方法
- 编写main方法,调用单例对象ConsoleLogger的log和warn方法
代码
//案例: 演示object单例对象继承特质
object ClassDemo03 {
//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("我是一条警告日志信息!")
}
}
6.trait中的成员变量和成员方法
需求
- 定义一个特质Hero, 添加具体字段name(姓名), 抽象字段arms(武器), 具体方法eat(), 抽象方法toWar()
- 定义一个类Generals, 继承Hero特质, 重写其中所有的抽象成员.
- 在main方法中, 创建Generals类的对象, 调用其中的成员.
代码
//案例: 演示特质中的成员
object ClassDemo04 {
//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()
}
}
对象混入trait(临时扩展技术)
1.概述
有些时候, 我们希望在不改变类继承体系的情况下, 对对象的功能进行临时增强或者扩展, 这个时候就可以考虑使用对象混入
技术了. 所谓的对象混入
指的就是: 在scala中, 类和特质之间无任何的继承关系, 但是通过特定的关键字, 却可以让该类对象具有指定特质中的成员
注:
- 抽象的实现为了表示类具体是什么
- 特质的实现为了表示类里面有什么
2.语法
val/var 对象名 = new 类 with 特质
3.案例
需求
- 创建Logger特质, 添加log(msg:String)方法
- 创建一个User类, 该类和Logger特质之间无任何关系.
- 在main方法中测试, 通过对象混入技术让User类的对象具有Logger特质的log()方法
代码
ect demo {
trait Logger{
def log(msg:String)
}
class User(){
}
def main(args: Array[String]): Unit = {
var user = new User() with Logger {
override def log(msg: String): Unit = println(s"看见消息:${msg}")
};
user.log("Logger混入User成功!")
}
}
使用trait实现适配器模式
1.设计模式
概述
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它并不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
分类
-
创建型
指的是: 需要创建对象的. 常用的模式有: 单例模式, 工厂方法模式
-
结构型
指的是: 类,特质之间的关系架构. 常用的模式有: 适配器模式, 装饰模式
-
行为型
指的是: 类(或者特质)能够做什么. 常用的模式有:模板方法模式, 职责链模式
2.适配器模式(结构型)
概述:
当特质中有多个抽象方法, 而我们只需要用到其中的某一个或者某几个方法时, 不得不将该特质中的所有抽象方法给重写了, 这样做很麻烦. 针对这种情况, 我们可以定义一个抽象类去继承该特质, 重写特质中所有的抽象方法, 方法体为空. 这时候, 我们需要使用哪个方法, 只需要定义类继承抽象类, 重写指定方法即可. 这个抽象类就叫: 适配器类. 这种设计模式(设计思想)就叫: 适配器设计模式.
结构:
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 ClassDemo06 {
//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, 重写support()和schoolchild()方法.
class GreenHand extends Player{
override def support(): Unit = println("我是辅助, B键一扣, 不死不回城!")
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个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。这就要用到模板方法设计模式了.
1.概述
在Scala中, 我们可以先定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤, 这就是: 模板方法设计模式
优点
- 扩展性更强
父类中封装了公共的部分, 而可变的部分交给子类来实现.
- 符合开闭原则
部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能.
缺点
-
类的个数增加, 导致系统更加庞大, 设计也更加抽象。
因为要对每个不同的实现都需要定义一个子类
-
提高了代码阅读的难度。
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构.
2.格式
class A { //父类, 封装的是公共部分
def 方法名(参数列表) = { //具体方法, 在这里也叫: 模板方法
//步骤1, 已知.
//步骤2, 未知, 调用抽象方法
//步骤3, 已知.
//步骤n...
}
//抽象方法
}
class B extends A {
//重写抽象方法
}
注: 抽象方法的个数要根据具体的需求来定, 并不一定只有一个, 也可以是多个.
3.案例
需求
- 定义一个模板类Template, 添加code()和getRuntime()方法, 用来获取某些代码的执行时间.
- 定义类ForDemo继承Template, 然后重写code()方法, 用来计算打印10000次"Hello,Scala!"的执行时间.
- 定义main方法, 用来测试代码的具体执行时间.
代码
object demo {
abstract class Template(){
def code();
def getRuntime(): Unit ={
//获取当前时间毫秒值
val start = System.currentTimeMillis()
//执行code方法
code();
//获取当前时间毫秒值
val end = System.currentTimeMillis();
//输出执行code方法运行所需时间
println(end-start);
}
}
class ForDemo extends Template(){
override def code(): Unit = {
for(i <- 1 to 10000) println("Hello, Scala!")
}
}
def main(args: Array[String]): Unit = {
new ForDemo().getRuntime();
}
}
使用trait实现职责链模式
1.概述
多个trait中出现了同一个方法, 且该方法最后都调用了super.该方法名(), 当类继承了这多个trait后, 就可以依次调用多个trait中的此同一个方法了, 这就形成了一个调用链。
执行顺序为:
-
按照
从右往左
的顺序依次执行.即首先会先从最右边的trait方法开始执行,然后依次往左执行对应trait中的方法
-
当所有子特质的该方法执行完毕后, 最后会执行父特质中的此方法.
这种设计思想就叫: 职责链设计模式.
注意: 在Scala中, 一个类继承多个特质的情况叫
叠加特质
.
2.格式
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()方法.
*/
3.案例
通过Scala代码, 实现一个模拟支付过程的调用链.
解释:
我们如果要开发一个支付功能,往往需要执行一系列的验证才能完成支付。例如:
- 进行支付签名校验
- 数据合法性校验
- …
如果将来因为第三方接口支付的调整,需要增加更多的校验规则,此时如何不修改之前的校验代码,来实现扩展呢?
这就需要用到: 职责链设计模式了.
步骤
- 定义一个Handler特质, 添加具体的handle(data:String)方法,表示处理数据(具体的支付逻辑)
- 定义一个DataValidHandler特质,继承Handler特质.
- 重写handle()方法,打印"验证数据", 然后调用父特质的handle()方法
- 定义一个SignatureValidHandler特质,继承Handler特质.
- 重写handle()方法, 打印"检查签名", 然后调用父特质的handle()方法
- 创建一个Payment类, 继承DataValidHandler特质和SignatureValidHandler特质
- 定义pay(data:String)方法, 打印"用户发起支付请求", 然后调用父特质的handle()方法
- 添加main方法, 创建Payment对象实例, 然后调用pay()方法.
代码
//案例: 演示职责链模式(也叫: 调用链模式)
object ClassDemo08 {
//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("苏明玉给苏大强转账10000元")
}
}
// 程序运行输出如下:
// 用户发起支付请求...
// 校验签名...
// 校验数据...
// 具体处理数据的代码(例如: 转账逻辑)
// 苏明玉给苏大强转账10000元