Scala的特质
1 特质入门
1.1 概述
有时我们遇到一些特定的需求,即在不影响当前继承体系的情况下,对某些类(或某些对象)的功能进行加强。例如:有猴子类和大象类,它们都有名字、年龄和吃的功能,但是部分猴子经过马戏团训练后,学会骑车了。那么骑车这个功能是不能定义到父类(动物类)或猴子类的,而是定义到特质中。Scala中的特质要用关键字trait
修饰。
1.2 特点
- 特质可以提高代码的复用性。
- 特质可以提高代码的扩展性和可维护性。
- 类与特质是继承关系,类与类只支持单继承,类与特质之间可以单继承,也可以多继承。
- Scala 的特质中可以有普通字段、抽象字段、普通方法、抽象方法。
- 如果特质只有抽象内容叫瘦接口
- 如果既有抽象内容又有具体内容叫富接口
1.3 语法
定义特质:
trait 特质名称 {
//普通字段
//抽象字段
//普通方法
//抽象方法
}
继承特质:
class 类 extends 特质1 with 特质2 {
//重写抽象字段
//重写抽象方法
}
注意:
- Scala中不管是类还是特质,继承关系用的都是
extends
关键字 - 如果要继承多个trait,则特质名之间使用
with
关键字隔开
1.4 示例:类继承单个trait
object ClassDemo {
//1.定义特质Logger
trait Logger {
def log(msg:String) //抽象方法
}
//2.定义类ConsoleLogger,继承Logger特质
class ConsoleLogger extends Logger {
override def log(msg:String):Unit = println(msg)
}
//3.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//4.测试类中的成员方法
val cl = new ConsoleLogger
cl.log("hi")
}
}
//hi
1.5 示例:类继承多个trait
object ClassDemo {
//1.定义特质MessageSender,添加send(msg:String)方法
trait MessageSender {
def send(msg:String) //抽象方法
}
//2.定义特质MessageReciever,添加receive()方法
trait MessageReceiver {
def receive() //抽象方法
}
//3.定义类MessageWorker,继承这两个特质,重写上述两个方法
class MessageWorker extends MessageSender with MessageReceiver {
override def send(msg:String):Unit = println(s"发送信息:${msg}")
override def receive():Unit = println("消息收到")
}
//3.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//4.测试类中的成员方法
val mw = new MessageWorker
mw.send('Hi')
mw.recieve()
}
}
//发送消息:Hi
//消息收到
1.6 示例:object继承trait
object ClassDemo {
//1.定义特质Logger
trait Logger {
def log(msg:String) //抽象方法
}
//1.定义特质Warning
trait Warning {
def warn(msg:String) //抽象方法
}
//2.定义单例对象ConsoleLogger,继承Logger和Warning特质
object ConsoleLogger extends Logger with Warning {
override def log(msg:String):Unit = println(s"控制台日志信息:${msg}")
override def warn(msg:String):Unit = println(s"控制台警告信息:${msg}")
}
//3.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//4.测试单例对象的方法
ConsoleLogger.log("我是人")
ConsoleLogger.warn("我是警告")
}
}
//控制台日志信息:我是人
//控制台警告信息:我是警告
1.5 示例:演示trait中的成员
object ClassDemo {
//1.定义特质Hero
trait Hero {
//具体字段
var name = 'mike'
//抽象字段
var arms:String
//具体方法
def eat() = println("吃饭")
//抽象方法
def toWar()
}
//2.定义类Generals,继承Hero特质,重写其中所有抽象成员
class Generals extends Hero {
override var arms: String = "大刀"
override def toWar():Unit = println(s"我${name}带着武器${arms}")
}
//3.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//4.测试
val g = new Generals
g.eat()
g.toWar()
}
}
//吃饭
//我mike带着大刀
2. 对象混入trait
有时我们希望在不改变继承体系的情况下,对对象的功能进行临时增强或扩展,这时可以考虑使用对象混入
技术。具体来说,指的是在Scala中,类和特质之间无任何的继承关系,但是通过特定的关键字,却可以让该类对象具有指定特质中的成员。
语法:
val/var 对象名 = new 类 with 特质
示例:
object ClassDemo {
//1.定义特质Logger
trait Logger {
def log(msg:String) = println(msg)
}
//2.定义类User,该类和Logger特质之间无任何关系
class User
//3.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//4.通过对象混入技术,让User类的对象具有Logger特质的Log()方法
val u = new User with Logger
u1.log("hi")
}
}
//hi
3. 使用trait实现适配器模式
3.1 设计模式简介
概述:
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它并不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
分类:
设计模式一共有23种,分为如下3类:
- 创建型 - 需要创建对象的。常用有:单例模式,工厂方法模式
- 结构型 - 类、特质之间的关系架构。常用有:适配器模式,装饰模式
- 行为型 - 类(或特质)能够做什么。常用有:模板方法模式,职责链模式
3.2 适配器模式
当某个特质中有多个抽象方法,而我们只需要用到某个或某几个方法时不得不将该特质所有抽象方法重写。针对这种情况,可以定义一个抽象类继承该特质,重写特质的所有抽象方法,方法体为空。这时我们需要使用哪个方法,只需要定义类继承抽象类,重写指定方法即可。
这个抽象类叫:适配器类
这种涉及模式叫:适配器设计模式
结构:
trait 特质A {
//抽象方法1
//抽象方法2
//抽象方法3
//...
}
abstract class 类B extends A { //适配器类
//重写抽象方法1,方法体为空
//重写抽象方法2,方法体为空
//重写抽象方法3,方法体为空
}
class 自定义类C extends 类B {
//需要用哪个方法,重写哪个方法即可
}
示例:
object ClassDemo {
//1.定义特质PlayLOL
trait PlayLOL {
def top()
def mid()
def adc()
def support()
def jungle()
def schoolChild()
}
//2.定义抽象类Player,继承playLOL特质,重写特质种所有抽象类,方法体都为空
abstract 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("不回城")
override def schoolChild(): Unit = println("挂机了")
}
//3.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//4.测试
val gh = new GreenHand
gh.support()
gh.schoolChild()
}
}
//不回城
//挂机了
4. 使用trait实现模板方法模式
设计一个系统时直到了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如:去银行办理业务,其中取号、排队和对工作人员评分的业务对每个客户都是一样的,可以在父类种实现,但是办理具体业务却因人而异,它可能时存款、取款或转账,可以延迟到子类中实现。这就用到模板方法设计模式了。
概述:
Scala中,我们可以先顶i一一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下,重新定义该算法的某些特定步骤,这就是:模板方法设计模式。
优点:
- 扩展性更强 - 父类封装了公共的部分,而可变的部分交给子类来实现
- 符合开闭原则 - 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能
缺点:
- 类的个数增加,导致系统更加庞大,设计也更加抽象 - 因为要对每个不同的实现都要定义一个子类
- 提高了代码阅读的难度 - 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构
格式:
class A {
def 方法名(参数列表) = { //具体方法,这里也叫:模板方法
//步骤1,已知
//步骤2,未知,调用抽象方法
//步骤3,已知
//步骤n,...
}
//抽象方法
}
class B extends A {
//重写抽象方法
}
示例:
object ClassDemo {
//1.定义模板类Template
abstract class Template {
//用来记录具体要获取执行时间的代码
def code()
//具体的计算规则
def getRuntime() = {
//获取当前的毫秒值
val start = System.currentTimeMillis()
//指定对应的代码
code()
//获取当前的毫秒值
val end = System.currentTimeMillis()
//两个毫秒值的差值,就是代码的执行时间
end - start
}
}
//2.定义类ForDemo继承Template,然后重写code()方法
class forDemo extends Template {
override def code(): Unit = for(i <- 1 to 10000) println('hi')
}
//3.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//4.创建ForDemo类对象
val fd = new ForDemo
//5.调用ForDemo类从Template类中继承过来的getRuntime()方法
println(fd.getRuntime())
}
}
//hi
//...(执行了10000次)
//hi
//138
5. 使用trait实现职责链模式
概述:
多个trait中出现了同一个方法,且该方法最后都调用了super.该方法名()
,当类继承了这多个trait之后,就可以依次调用多个trait中的此同一个方法了,这就形成了一个调用链。
执行顺序:
- 按从右到左的顺序依次执行
- 当所有子特质的该方法执行完毕后,最后会执行父特质中的此方法
这种设计思想就叫:职责链设计模式
在Scala中,一个类继承多个特质的情况叫叠加特质
格式:
trait A { //父特质
def show() //假设方法名叫:show
}
trait B extends A{ //子特质,根据需求可以定义多个
override def show() = {
//具体的代码逻辑
super.show()
}
}
trait C extends A{
override def show() = {
//具体的代码逻辑
super.show()
}
}
trait D extends B with C { //具体的类,用来演示:叠加特质
def 方法名() = { //这里可以时该类自己的方法,不一定是show()方法
//具体的代码逻辑
super.show() //这里就构成了:调用链
}
}
/*
执行顺序为:
1.先执行类D中自己的方法
2.再执行特质C中的show()方法
3.再执行特质B中的show()方法
4.最后执行特质A中的show()方法
*/
示例:
object ClassDemo {
//1.定义Handler特质,添加具体的handle()方法,表示除了数据
trait Handler {
def handle(data:String) = { //data记录的是:用户发起的具体支付数据
println("具体的处理数据")
println(data)
}
}
//2.定义DataValidHandler特质,继承Handler特质
trait DataValidHandler extends Handler {
//重写handle()方法,然后调用父特质的handle()方法
override def handle(data:String): Unit = {
//打印“验证数据”
println("验证数据")
//核心:调用父特质的handle()方法
super.handle(data)
}
}
//3.定义SignatureValidHandler特质,继承Handler特质
trait SignatureValidHandler extends Handler {
//重写handle()方法,然后调用父特质的handle()方法
override def handle(data:String): Unit = {
//打印“检查签名”
println("检查签名")
//核心:调用父特质的handle()方法
super.handle(data)
}
}
//4.创建Payment类,继承DataValidHandler特质和SignatureValidHandler特质
class Payment extends DatValidHandler with SignatureValidHandler { //叠加特质
//定义pay()方法,然后调用父特质的handle()方法
//打印“用户发起支付请求”
println("用户发起支付请求")
//调用父特质的handle()方法
super.handle(data) //这就构成了调用链
}
//5.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//6.测试
val p = new Payment
p.pay("我转账了")
}
}
//用户发起支付请求
//检查签名
//验证数据
//具体的处理数据
//我转账了
6. trait的构造机制
-
每个特质只有一个无参构造器 (即trait也有构造代码,但和类不一样,特质不能由构造器参数)
-
遇到一个类继承另一个类以及多个trait的情况,创建该类实例时构造器执行顺序:
- 执行父类构造器
- 从左到右依次执行 trait 的构造器
- 如果trait 有父 trait,先执行父 trait
- 如果多个 trait 有相同的父 trait,父 trait 构造器只初始化一次
- 执行子类构造器
示例:
object ClassDemo {
//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")
}
//4.创建Student类,继承Person类及MyLogger,TimeLogger特质
class Student extends Person with MyLogger with TimeLogger {
println("执行Student")
}
//5.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//6.测试
val s = new Student
}
}
//执行Person
//执行Logger
//执行MyLogger
//执行TimeLogger
//执行Student
7. trait继承class
Scala中,trait也可以继承class。特质会将类中的成员都继承下来。
格式:
class 类A {
//成员变量
//成员方法
}
trait B extends A {
}
示例:
object ClassDemo {
//1.定义message类
class Message {
def printMsg() = println("hi")
}
//2.创建Logger特质,继承Message类
trait Logger extends Message
//3.定义ConsoleLogger特质,继承Logger特质
trait ConsoleLogger extends Logger
//4.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//5.测试
val cl = new ConsoleLogger
cl.printMsg()
}
}
//hi
8. 案例:程序员
需求:
整个继承体系的共性内容定义到父类中,整个继承体系的扩展内容定义到父特质中
object ClassDemo {
//1.定义Programmer类,表示所有程序员
abstract class Programmer {
var name = ""
var age = 0
def eat()
def skill()
}
//2.定义JavaProgrammer类,表示所有Java程序员,继承Programmer类
class JavaProgrammmer {
override def eat() = println("Java吃饭")
override def skill() = println("Java精通")
}
//3.定义PythonProgrammer类,表示所有Python程序员,继承Programmer类
class PythonProgrammmer {
override def eat() = println("Python吃饭")
override def skill() = println("Python精通")
}
//4.创建BigData特质,表示大数据技术
trait BigData {
def learningBigData() = {
println("大数据精通")
}
}
//5.定义PartJavaProgrammmer类,表示精通Java和大数据的程序员,继承JavaProgrammer类和BigData特质
class PartJavaProgrammer extends JavaProgrammer with BigData {
override def eat() = Println("Java大数据吃饭")
override def skill() = {
super.skill()
learningBigData()
}
}
//6.定义PartPythonProgrammmer类,表示精通Python和大数据的程序员,继承PythonProgrammer类和BigData特质
class PartPythonProgrammer extends PythonProgrammer with BigData {
override def eat() = Println("Python大数据吃饭")
override def skill() = {
super.skill()
learningBigData()
}
}
//7.定义main方法,作为程序的主入口
def main(args:Array[String]):Unit = {
//8.测试
//8.1测试普通Java程序员
val jp = new JavaProgrammer
jp.name = "lee"
jp.age = 23
println(jp.name, jp.age)
jp.skill()
jp.eat()
//8.2测试精通Java+大数据的程序员
val pjp = new JavaProgrammer
pjp.name = "mike"
pjp.age = 33
println(pjp.name, pjp.age)
pjp.skill()
pjp.eat()
//8.3测试普通Python程序员
//8.4测试精通Python+大数据的程序员
}
}
//(lee,23)
//Java精通
//Java吃饭
//(mike,33)
//Java大数据精通
//Java大数据吃饭