4 特质(trait)
- 特质是scala中代码复用的基础单元
- 它可以将方法和字段定义封装起来,然后添加到类中
- 与类继承不一样的是,类继承要求每个类都只能继承
一个
超类,而一个类可以添加任意数量
的特质。 - 特质的定义和抽象类的定义很像,但它是使用
trait
关键字
接下来,我们就来学习trait的几种用法。
7.1 作为接口使用
- 使用
extends
来继承trait(scala不论是类还是特质,都是使用extends关键字) - 如果要继承多个trait,则使用
with
关键字
案例1:继承单个trait
实现步骤:
- 创建一个Logger特质,添加一个接受一个String类型参数的log抽象方法
- 创建一个ConsoleLogger类,继承Logger特质,实现log方法,打印消息
- 添加main方法,创建ConsoleLogger对象,调用log方法
示例代码:
trait Logger { // 抽象方法 def log(msg:String) } class ConsoleLogger extends Logger { override def log(msg: String): Unit = println(msg) } object LoggerTrait { def main(args: Array[String]): Unit = { val logger = new ConsoleLogger logger.log("控制台日志: 这是一条Log") } }
案例2:继承多个trait
实现步骤:
- 在上一个案例的基础上,创建一个MessageSender特质,添加接受一个String类型参数的send方法
- 再让ConsoleLogger实现一个MessageSender特质,并实现它的send方法,打印"发送消息…"
- 在main中调用,send方法
示例代码:
trait Logger { // 抽象方法 def log(msg:String) } trait MessageSender { def send(msg:String) } class ConsoleLogger extends Logger with MessageSender { override def log(msg: String): Unit = println(msg) override def send(msg: String): Unit = println(s"发送消息:${msg}") } object LoggerTrait { def main(args: Array[String]): Unit = { val logger = new ConsoleLogger logger.log("控制台日志: 这是一条Log") logger.send("你好!") } }
案例3:object继承trait
实现步骤:
- 创建一个LoggerForObject特质,添加一个接受一个String类型参数的log抽象方法
- 创建一个ConsoleLogger的object,实现LoggerForObject特质,实现log方法,打印消息
- 编写main方法,调用ConsoleLogger的log方法
trait LoggerForObject { // 抽象方法 def log(msg:String) } // object也可以继承trait object ConsoleLogger extends LoggerForObject { override def log(msg: String): Unit = println(s"控制台信息 $msg") } object ObjectTrait { def main(args: Array[String]): Unit = { ConsoleLogger.log("程序退出") } }
- 在trait中可以定义抽象方法,不写方法体就是抽象方法
- 和继承类一样,使用extends来继承trait
- 继承多个trait,使用with关键字
- 单例对象也可以继承trait
4.2 定义具体的方法
和类一样,trait中还可以定义具体的方法。·
案例:在trait中定义具体方法
实现步骤:
- 定义一个LoggerDetail特质
- 添加接受一个String类型的log方法,并打印参数
- 定义一个UserService类,实现LoggerDetail特质
- 添加add方法,调用log方法打印"添加用户"
- 添加main方法
- 创建UserService对象实例
- 调用add方法
示例代码:
trait LoggerDetail { // 在trait中定义具体方法 def log(msg:String) = println(msg) } class UserService extends LoggerDetail { def add() = log("添加用户") } object MethodInTrait { def main(args: Array[String]): Unit = { val userService = new UserService userService.add() } }
4.3 定义具体方法和抽象方法
- 在trait中,可以混合使用具体方法和抽象方法
- 使用具体方法依赖于抽象方法,而抽象方法可以放到继承trait的子类中实现,这种设计方式也称为模板模式
案例:实现一个模板模式
实现步骤:
- 添加一个LoggerFix特质
- 添加一个log抽象方法,接收String参数
- 添加一个info具体方法,接收String参数,调用log方法,参数添加"INFO:"
- 添加一个warn具体方法,接收String参数,调用log方法,参数添加"WARN:"
- 添加一个error具体方法,接收String参数,调用log方法,参数添加"ERROR:"
- 创建ConsoleLoggerFix类
- 实现LoggerFix特质
- 实现log方法,打印参数
- 添加main方法
- 创建ConsoleLoggerFix类对象
- 调用info方法
- 调用warn方法
- 调用error方法
示例代码:
trait Logger08 { // 抽象方法 def log(msg:String) // 具体方法(该方法依赖于抽象方法log def info(msg:String) = log("INFO:" + msg) def warn(msg:String) = log("WARN:" + msg) def error(msg:String) = log("ERROR:" + msg) } class ConsoleLogger08 extends Logger08 { override def log(msg: String): Unit = println(msg) } object Trait08 { def main(args: Array[String]): Unit = { val logger08 = new ConsoleLogger08 logger08.info("这是一条普通信息") logger08.warn("这是一条警告信息") logger08.error("这是一条错误信息") } }
4.4 定义具体的字段和抽象的字段
- 在trait中可以定义具体字段和抽象字段
- 继承trait的子类自动拥有trait中定义的字段
- 字段直接被添加到子类中
案例:在trait中定义具体的字段和抽象的字段
实现步骤:
- 创建LoggerEx特质
- 定义一个sdf具体字段,类型为SimpleDateFormat,用来格式化日志(显示到时间)
- 创建一个INFO具体字段,类型为String,初始化为:“信息:当前日期”
- 创建一个TYPE抽象字段,类型为String
- 创建一个log抽象方法,参数为String类型
- 创建ConsoleLoggerEx类
- 实现LoggerEx特质
- 实现TYPE抽象字段,初始化为"控制台"
- 实现log抽象方法,分别打印TYPE字段,INFO字段和参数
- 添加main方法
- 创建ConsoleLoggerEx类对象
- 调用log方法
示例代码:
trait LoggerEx { // 具体字段 val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm") val INFO = "信息:" + sdf.format(new Date) // 抽象字段 val TYPE:String // 抽象方法 def log(msg:String) } class ConsoleLoggerEx extends LoggerEx { // 实现抽象字段 override val TYPE: String = "控制台" // 实现抽象方法 override def log(msg:String): Unit = print(s"$TYPE$INFO $msg") } object FieldInTrait { def main(args: Array[String]): Unit = { val logger = new ConsoleLoggerEx logger.log("这是一条消息") } }
4.5 实例对象混入trait
- trait还可以混入到
实例对象
中,给对象实例添加额外的行为 - 只有混入了trait的对象才具有trait中的方法,其他的类对象不具有trait中的行为
- 使用with将trait混入到实例对象中
案例:将一个特质混入到一个对象中
实现步骤:
- 创建一个LoggerMix混入
- 添加一个log方法,参数为String类型
- 打印参数
- 创建一个UserService类
- 添加main方法
- 创建UserService对象,还如LoggerMix特质
- 调用log方法
示例代码:
trait LoggerMix { def log(msg:String) = println(msg) } class UserService object FixedInClass { def main(args: Array[String]): Unit = { // 使用with关键字直接将特质混入到对象中 val userService = new UserService with LoggerMix userService.log("你好") } }
4.6 trait调用链
责任链模式
需求:
类继承了多个trait后,可以依次调用多个trait中的同一个方法,只要让多个trait中的同一个方法在最后都依次执行super关键字即可。类中调用多个tait中都有这个方法时,首先会从最右边的trait方法开始执行,然后依次往左执行,形成一个调用链条。
案例:实现一个模拟支付过程的调用链
实现步骤:
- 定义一个HandlerTrait特质
- 定义一个具体的handler方法,接收String参数,打印"处理数据…"
- 定义一个DataValidHandlerTrait,继承HandlerTrait特质
- 重写handler方法
- 打印"验证数据"
- 调用父特质的handler方法
- 定义一个SignatureValidHandlerTrait,继承HandlerTrait特质
- 重写Handler方法
- 打印"检查签名"
- 调用父特质的handler方法
- 创建一个PaymentService类
- 继承DataValidHandlerTrait
- 继承SignatureValidHandlerTrait
- 定义pay方法
- 打印"准备支付"
- 调用父特质的handler方法
- 添加main方法
- 创建PaymentService对象实例
- 调用pay方法
示例:
// 支付数据处理 trait HandlerTrait { def handle(data: String) = { println("处理数据...") } } // 数据校验处理 trait DataValidHandlerTrait extends HandlerTrait { override def handle(data: String) = { println("验证数据...") super.handle(data) } } // 签名校验处理 trait SignatureValidHandlerTrait extends HandlerTrait { override def handle(data: String) = { println("检查签名...") super.handle(data) } } // 支付服务 class PaymentService extends DataValidHandlerTrait with SignatureValidHandlerTrait { def pay(data:String) = { println("准备支付...") this.handle(data) } } object PaymentService { def main(args: Array[String]) { val payService = new PaymentService() payService.pay("signature:10233123||md5:123889a3d5s1f6123||data:{order:001,money:200}") } } // 程序运行输出如下: // 准备支付... // 检查签名... // 验证数据... // 处理数据...
4.7 trait的构造机制
- trait也有构造代码,但和类不一样,特质不能有构造器参数
- 每个特质只有**
一个无参数
**的构造器。
一个类继承另一个类、以及多个trait,当创建该类的实例时,它的构造顺序如下:
- 执行父类的构造器
从左到右
依次执行trait的构造器
- 如果trait有父trait,先构造父trait,如果多个trait有同样的父trait,则只初始化一次
- 执行子类构造器
案例:演示trait的构造顺序
实现步骤:
- 创建一个Person_One特质,在构造器中打印"执行Person构造器!"
- 创建一个Logger_One特质,在构造器中打印"执行Logger构造器!"
- 创建一个MyLogger_One特质,继承自Logger_One特质,,在构造器中打印"执行MyLogger构造器!"
- 创建一个TimeLogger_One特质,继承自Logger_One特质,在构造器中打印"执行TimeLogger构造器!"
- 创建一个Student_One类,继承自Person_One、MyLogger_One、TimeLogger_One特质,在构造器中打印"执行Student构造器!"
- 添加main方法,实例化Student_One类,观察输出。
示例代码:
class Person_One { println("执行Person构造器!") } trait Logger_One { println("执行Logger构造器!") } trait MyLogger_One extends Logger_One { println("执行MyLogger构造器!") } trait TimeLogger_One extends Logger_One { println("执行TimeLogger构造器!") } class Student_One extends Person_One with MyLogger_One with TimeLogger_One { println("执行Student构造器!") } object exe_one { def main(args: Array[String]): Unit = { val student = new Student_One } } // 程序运行输出如下: // 执行Person构造器! // 执行Logger构造器! // 执行MyLogger构造器! // 执行TimeLogger构造器! // 执行Student构造器!
4.8 trait继承class
- trait也可以继承class
- 这个class就会成为所有该trait子类的超级父类。
案例:定义一个特质,继承自一个class
实现步骤:
- 创建一个MyUtils类
- 定义printMsg方法,接收String参数,并打印参数
- 创建一个Logger_Two特质
- 继承自MyUtils
- 定义log方法,接收String参数,并打印一个前缀和参数
- 创建一个Person_Three类
- 实现String类型name字段的主构造器
- 继承Logger_Two特质
- 实现sayHello方法
- 调用log,传入"Hi, I‘m "加上name字段
- 调用printMsg,传入"Hello, I’m "加上name字段
- 添加main方法
- 创建一个Person_Three对象
- 调用sayHello方法
示例:
class MyUtil { def printMsg(msg: String) = println(msg) } trait Logger_Two extends MyUtil { def log(msg: String) = this.printMsg("log: " + msg) } class Person_Three(val name: String) extends Logger_Two { def sayHello { this.log("Hi, I'm " + this.name) this.printMsg("Hello, I'm " + this.name) } } object Person_Three{ def main(args: Array[String]) { val p=new Person_Three("Tom") p.sayHello //执行结果: // log: Hi, I'm Tom // Hello, I'm Tom } }