-
2021-11-17 15:24:51
10 泛型、trait和生命周期
每一种编程语言都有高效处理重复概念的工具,Rust使用的是泛型。泛型是具体类型或其它抽象类型的替代。我们可以表达泛型的属性,比如他们的行为如何与其它类型的泛型相关联,而不许需要在编写或者编译代码时知道它们在这里实际上代表的是什么
我们编写一个函数,想让它对多种类型的参数都具有处理能力,而不仅仅是针对某种具体类型的参数定义函数。这个时候我们就可以指定函数的参数类型是泛型,而不是某种具体类型。我们之前使用了Option、Vec!和HashMap<K,V>
提取函数来减少重复
在此之前,我们先来回顾一个不使用泛型就解决代码重复的技术,提取函数
fn main() { let number_list = vec![34,25,50,100,65]; let mut largest = number_list[0]; for number in number_list { if number > largest { largest = number }; }; println!("the largest number is {}",largest) }
我们先遍历了一个vector,求其元素的最大值,那如果我们要遍历另一个元素的话,就需要再重复一下这些代码,现在我们用函数来做个提取
fn largest(list:&[i32])->i32{ let mut largest = list[0]; for &item in list { if item > largest { largest = item }; }; largest } fn main(){ let number_list = vec![34,50,25,100,65]; let result = largest(&number_list); println!("{}",result) }
通过函数抽象,我们就不用再重用代码了,只需要调用这个比较逻辑(我们新定义好的函数)即可
但是!我们在这里寻找的是vec中的最大值,如果我们要寻找char、slice中的最大值呢?下面我们来着手解决这个问题
10.2 trait:定义共享的行为
trait告诉编译器某个特定的类型可能拥有与其他了类型共享的功能。因此我们可以使用trait来抽象定义一组共享的行为,trait有点像其它语言中的接口。可以使用trait bounds指定泛型是任何拥有特定行为的类型
定义trait
一个类型的行为由其可供调用的方法构成,如果不同的类型可以调用相同的方法,那这些类型就共享这种相同的行为.而trait定义就是将这种方法签名组合起来的方法,而它的目的就是定义一个行为集合,这些行为可以实现我们的某个目的
我们来看一个例子:有两个结构体NewsArticle 和 Tweet ,前者存放新闻故事,后者存放元数据,类似转推或回复。但我们现在想要创建一个多媒体聚合库,它显示的是NewsArticle 或 Tweet中的数据总结。每个结构体都对应一个行为(方法),这样,我们就可以定义一个 trait
pub trait Summary { fn summarize(&self)->String; }
关于定义trait的语法格式,非常简单。使用关键字trait + trait名称,然后花括号中写具体实现的方法,可以写一个也可以写多个,并且每个都以分号结尾
为类型实现trait
我们把上面的案例再来剖析下,然后在这个两个struct上实现trait,使用impl + trait名称 + for + 具体类型名称 语法格式
pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } impl Summary for NewsArticle { fn summarize(&self)->String{ format!("{},by {} ({})",self.headline,self.location,self.author) } } pub struct Tweet { pub username:String, pub content:String, pub reply:bool, pub retweet:bool, } impl Summary for Tweet { fn summarize(&self)->String{ format!("{}:{}",self.username,self.content) } }
一旦实现了trait,我们就可以调用了
fn main(){ let tweet = Tweet{ username: String::from("horse_ebooks"), content:String::from("of course, as you probably already know, people"), reply:false, retweet:false }; println!("1 new tweet:{}",tweet.summarize()) } //1 new tweet:horse_ebooks:of course, as you probably already know, people
不过这边有几个注意事项需要提醒一下:
当定义的trait和类型是不在同一作用域时,使用trait要先将其引入作用域。假如上述Summary trait 和NewsArticle、Tweet都在礼包。rs中定义。并且lib.rs在aggregator crate下,如果别人想要使用这个crate的功能时,需要用use aggregator::Summary; 来引入。也就是说只有当trait或者和要实现trait的类型位于crate的本地作用域时,才能为该类型实现trait
但是不能为外部类新实现外部trait。例如不能在aggregator 中为Vec实现Display trait。这是因为Display和Vec都定义与标准库中,它们并不位于aggregator crate本地作用域中。这个限制被称为相干性的程序属性的一部分,或者更具体的说是孤儿规则。其得名于不存在父类型。这条规则确保了其他人编写的代码不会破坏你的代码,,反之亦然,没有这条规则的话,两个crate分别对相同的类型实现相同的trait,而Rust无从得知应该使用哪一个实现
默认实现
有时候为trait里的方法提供默认行为,而不是针对每个具体类型都定义自己的行为很有用。这样,为某个特定类型实现trait时,可以选择保留或者重载每个方法的默认行为
如下为Summary trait的summarize指定了一个默认的字符串值,而不是像实例中那样只定义方法签名
pub trait Summary { fn summarize(&self)->String{ String::from("Read more...") } }
如果想要对Tweet实例使用这个默认实现,可以通过impl Summary for Tweet {} 指定一个空的impl块,如下:
impl Summary for Tweet {} fn main(){ let tweet = Tweet{ username: String::from("horse_ebooks"), content:String::from("of course, as you probably already know, people"), reply:false, retweet:false }; println!("1 new tweet:{}",tweet.summarize()) } //1 new tweet:Read more...
我们再来看一个比较有趣的调用,之前我们有提到一个trait中可以定义多个类型的方法(也可以是默认的方法),这个有意思的点是在实际操作中,我们可以trait中的一个方法调用另一个方法
pub trait Summary { fn summarize_auther(&self)->String; fn summarize(&self)->String{ format!("(Read more from {}...)",self.summarize_auther()) } } impl Summary for Tweet { fn summarize_auther(&self) -> String{ format!("@{}",self.username) } } fn main(){ let tweet = Tweet{ username: String::from("horse_ebooks"), content:String::from("of course, as you probably already know, people"), reply:false, retweet:false }; println!("1 new tweet:{}",tweet.summarize()) } //1 new tweet:(Read more from @horse_ebooks...)
注意:无法从相同方法的重载实现中调用默认方法
trait 作为参数
同样的,trait也可以作为函数的参数!我们来看看例子,trait 作为参数类型是写作 impl trait名称,如下,函数体内可以调用trait内的任何方法,即只要是实现了Summary的类型都可以
pub trait Summary { fn summarize_auther(&self)->String; fn summarize(&self)->String{ format!("(Read more from {}...)",self.summarize_auther()) } } pub fn notify(item:impl Summary){ println!("Breaking news! {}",item.summarize()) }
Trait bound 语法
impl Trait 实际上是一种语法糖,称为trait bound,它看起来像:
pub fn notify<T:Summary>(item:T) { println!("Breaking news! {}",item.summarize()); }
这与之前的例子相同,不过稍微冗长了一些,trait bound与泛型参数声明在一起,位于尖括号中的冒号后面
impl trait很方便,适用于简单的例子,trait bound则适用更复杂的场景,以下是impl trait写法:
pub fn notify(item1:impl Summary,item2:impl Summary){...}
这里的item1和item2允许是不同的类型,如果希望强制它们都是相同的类型,只能用 trait bound
pub fn notify<T:Summary>(item1:T,item2:T){...}
通过+ 指定多个 trait bound
item要实现两个trait
pub fn notify(item: impl Summary + Display) {...}
泛型的trait bound 也可以 +
pub fn notify<T:Summary + Dispaly>(item:T) {...}
通过指定这两个trait bound,notify 的函数体可以调用summarize 并使用{} 来格式化item
通过where 简化trait bound
使用过多的trait bound会让代码变的难以阅读,我们可以用where来改写,让代码更容易阅读:
trait bound写法:
fn some_function<T:Display + Clone, U: clone + Debug>(t:T,u:U) -> i32 {}
where 改写,如下就易阅读多了
fn some_function<T,U>(t:T,u:U)->i32{ where T: Display + Clone, U:Clone + Debug }
返回实现了trait的类型
trait 既然可以作为参数类型,那当然可以作为返回值类型,这样,函数就会返回一个实现了Summary trait的类型,但是不能确定具体类型是什么,只知道这个类型实现了Summary trait, 调用方也并不知情
这点在闭包和迭代器的场景中十分有用,后面我们会介绍它,闭包和迭代器创建只有编译器知道的类型,或者非常非常长的类型,impl trait允许你简单的指定函数返回一个Iterator而无需写出实际冗长的类型
fn return_summariable()->impl Summary{ Tweet{ username: String::from("horse_ebooks"), content:String::from("of course, as you probably already know, people"), reply:false, retweet:false, } }
不过这只适合返回单一类型的情况,如下这种返回NewsArticle 或 Tweet就无法编译,这是因为impl trait 工作方式的限制,后面我们会在讲“为使用不同类型的值而设计的trait对象”部分会介绍如何编译这样一个函数
fn returns_summarizable(switch:bool) -> impl Summary { if switch { NewsArticle { headline:String::from("Penguins win the Staley Cup Championship!"), location:String::from("Pittshburgh,PA,USA"), author:String::from("Iceburgh"), content:String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL"), } } else { Tweet{ username: String::from("horse_ebooks"), content:String::from("of course, as you probably already know, people"), reply:false, retweet:false, } } }
使用 trait bounds 来修复largest 函数
我们来看看原来的例子:
fn main(){ let number_list = vec![34,50,25,100,65]; let result = largest(&number_list); println!("{}",result);// 100 let char_list = vec!['y','m','a','q']; let result = largest(&char_list); println!("{}",result)// y }
运行输出如下结果:
error[E0369]: binary operation `>` cannot be applied to type `T` --> src\main.rs:36:17 | 36 | if item > largest{ | ---- ^ ------- T | | | T | help: consider restricting type parameter `T` | 32 | fn largest<T: std::cmp::PartialOrd>(list:&[T]) -> T{
在largest 函数体中我们想要使用 > 来比较两个T类型的值,这个运算符是定义于标准库 trait std::cmp::PartialOrd的一个默认的方法,所以需要在T的trait bound 中指定 PartialOrd,这样largest 函数可以用于任何可以比较大小的类型的slice。因为PartialOrd位于preclude中所以并不需要手动将其引入作用域,修改largest 的签名
fn largest<T:PartialOrd>(list:&[T]) -> {}
但是如果编译代码的化,会出现一些不同的错误
error[E0508]: cannot move out of type `[T]`, a non-copy slice --> src/main.rs:2:23 | 2 | let mut largest = list[0]; | ^^^^^^^ | | | cannot move out of here | help: consider using a reference instead: `&list[0]` error[E0507]: cannot move out of borrowed content --> src/main.rs:4:9 | 4 | for &item in list.iter() { | ^---- | || | |hint: to prevent move, use `ref item` or `ref mut item` | cannot move out of borrowed content
错误核心:cannot move out of type
[T]
, a non-copy slice这是因为像 i32 和 char这种类型实现了copy trait(已知大小并可以存储在栈上),但是largest函数改成使用泛型后,现在list的参数就有可能没有实现copy trait,所以我们可能不能将list[0]的值移动,这就导致上面的错误
我们可以只调用实现了copy的类型,如下是可以编译的代码
fn largest<T:PartialOrd + Copy>(list:&[T]) ->T { let mut largest = list[0]; for &item in list.iter(){ if item >largest { largest = item; } } largest } fn main(){ let number_list = vec![34,50,25,100,65]; let result = largest(&number_list); println!("The largest vnumber is {}",result); let char_list = vec!['y','m','a','q']; let result = largest(&char_list); println!("The largest char is {}",result) } // The largest vnumber is 100 The largest char is y
如果并不希望限制largest 函数只能用于实现了Copy trait的类型,我们可以在T的trait bounds中指定clone而不是Copy。并克隆slice的每个值使得largest函数拥有其所有权,使用clone函数意味着对于类似于String这样拥有堆上数据的类型。会潜在分配更多堆上空间而堆分配在涉及大量数据时可能会相当缓慢
另一种largest的实现方式是返回在slice中T值的引用,如果我们讲函数返回值从T改为&T并改变函数体使其能返回一个引用。我们将不需要任何clone或Copy的trait bounds而且也不会有任何堆分配
使用trait bound 有条件的实现方法
通过使用带有trait bound的泛型参数的impl块,可以有条件的只为那些实现了特定trait类型实现方法,如下
struct Pair<T> { x:T, y:T, } impl<T> Pair<T>{ fn new(x:T,y:T) -> Self{ Self { x, y, } } } impl<T:Dispaly + PartialOrd> Pair<T>{ fn cmp_display(&self){ if self.x >= self.y{ println!("the largest member is x = {}",self.x); }else { println!("the largest member is y = {}",self.y); } } }
也可以对任何实现了特定trait的类型有条件的实现trait,对任何满足特定trait bound 的类型实现trait被称为 blanket implementations。它们被广泛的用于Rust标准库中。例如,标准库为任何实现了display trait的类型实现了ToSorting trait,这个impl 块看起来像
impl<T:Display> ToString for T {...}
因为标准库有了这些blanket implementation,我们可以对任何实现了Display trait 的类型调用由ToString 定义的to_string 方法。例如:可以将整型转换为对应的String值,因为整型实现了Display:
let s = 3.to_string();
blanket implementation 会出现在trait 文档的“Implementers"部分
trait和trait bound 让我们使用泛型类型参数来减少重复,但是也告知了编译器泛型类型应拥有哪些行为,编译器会检查具体类型是否提供了正确的行为,这种检错机制是很好的
还有一种泛型是声明周期,不同于其它泛型,确保类型拥有期望的行为,而生命周期则有助于我们引用时,这些类型一直有效
更多相关内容 -
PHP中Trait及其应用详解
2020-12-18 22:13:50而Trait则避免了这点。下面通过简单的额例子来进行对比说明。 1. 继承 VS 多态 VS Trait 现在有Publish.php和Answer.php这两个类。要在其中添加LOG功能,记录类内部的动作。有以下几种方案: 继承 多态 ... -
浅谈PHP中的Trait使用方法
2020-10-17 06:38:59主要介绍了PHP中的Trait使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
async-trait-static:诸如async-trait之类的功能,避免使用Box和dyn
2021-05-18 05:27:20诸如async-trait ,请避免使用Box和dyn 。 您可以在特征中使用异步键盘输入,而无需分配。 感谢crate ,从中提供了一些代码。 警告:此板条箱使用了一些不稳定甚至不完整的功能。 您将从编译器获得一些警告。 ... -
async-trait:异步特征方法的类型擦除
2021-04-27 23:54:17异步特征方法 Rust 1.39中对异步/等待语言功能的第一轮稳定化不包括对特征中的异步fn的支持。 尝试在特征中包含异步fn会产生... 这里唯一需要注意的是,我们在包含异步fn的trait和trait impls之上编写了一个#[async_ -
Scala Trait(特质).html
2020-05-13 10:28:50Scala Trait(特征) 1.Scala中没有接口(interface)的概念 2.特质用于在类之间共享程序接口和字段,类似Java接口 3.特质是字段和方法的集合,可以提供字段和方法实现 4.类和单例对象都可以扩展特质(extends) 5.... -
Laravel中Trait的用法实例详解
2020-12-19 21:29:56本文实例讲述了Laravel中Trait的用法。分享给大家供大家参考,具体如下: 看看PHP官方手册对Trait的定义: 自 PHP 5.4.0 起,PHP 实现了代码复用的一个方法,称为 traits。 Traits 是一种为类似 PHP 的单继承语言而... -
Trait
2022-04-22 11:52:21trait 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 trait bounds 指定泛型是任何拥有特定行为的类型。Rust Trait
什么是 Trait
PHP trait
摘自php.net 对
trait
的介绍:PHP 实现了一种代码复用的方法,称为 trait。
Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。
Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。
PHP Trait 示例:
/** * Trait Animal * 动物特型 */ trait Animal { /** * 无法定义常量,使用变量代替 * 物种 * @var string */ private $SPECIES = ""; /** * 抽象方法 * 获取名称方法 * @return string */ abstract function name() :string; /** * 默认实现方法 * 自我介绍方法 * @return string */ function say() :string { $name = $this->name(); return "你好,我是一只{$this->SPECIES},我的名字叫\"{$name}\""; } }
Rust trait
摘自rust语言官网 对
trait
的介绍:trait 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 trait bounds 指定泛型是任何拥有特定行为的类型。
注意:trait 类似于其他语言中的常被称为 接口(interfaces)的功能,虽然有一些不同。
这里【其他语言】应该不包括PHP,因为PHP也有trait。
简单来说,Rust Trait 是通过
trait
关键字定义的一组方法(function)、类型(type)、常量(constant),实现代码复用。与 PHPtrait
类似,PHP Trait 没有type
和constant
,但是可以定义变量(field)。Rust Trait 示例:
/// 动物特型 trait Animal { /// 类型 /// 错误类型 type Err; /// 常量 /// 物种名称 const SPECIES: &'static str; /// 抽象方法 /// 获取名称方法 fn name(&self) -> &str; /// 默认实现方法 /// 自我介绍方法 fn say(&self) -> String { format!(r#"你好,我是一只{},我的名字叫"{}""#, Self::SPECIES, self.name()) } }
Trait 的实现
derive 宏
通过
derive
宏实现 trait,前提是已经定义了对应的proc_macro_derive
宏方法,且内部类型都已经实现了该 trait。/// Debug derive 宏核心库已经定义 /// 内部的 `name`(String)、`age`(u32) 都已经实现了 Debug trait,所以可以直接通过 derive 自动实现 Debug trait #[derive(Debug)] struct Bird { name: String age: u32 }
手动实现
impl
trait name
forstruct or enum
{}/// 鸟是动物 impl Animal for Bird { type Err = (); const SPECIES: &'static str = "鸟"; fn name(&self) -> &str { self.name.as_str() } }
孤儿规则
当你为某类型实现某 trait 的时候,该类型或者trait至少有一个是在当前 crate 中定义的,不能为第三方的类型实现第三方的 trait 。否则可能会影响第三方 crate 中的行为。
Rust Trait 用途
在PHP中,
trait
主要是解决class
单继承的限制,但 Rust 中没有class
和继承的概念,也就没有这方面的用途。Rust Trait 用途主要有以下几个:接口抽象
这个作用与 PHP trait 一样,但是PHP有专门的定义接口(
interface
)的功能;/** * PHP 接口抽象 trait 形式 */ trait Fly { /** * @throws Exception */ function fly(); } /** * PHP 接口抽象 interface 形式 */ interface Fly { /** * @throws Exception */ function fly(); }
/// Rust 接口抽象 /// 定义一个 `Fly` 接口 trait Fly { fn fly() -> Result<(), ()>; } /// 鸟能飞,实现 Fly 接口 impl Fly for Bird { fn fly(&self) -> Result<(), ()> { println!("芜湖起飞"); Ok(()) } }
泛型约束
rust:
/// 定一个一个动物飞行方法 /// 一边飞一边自我介绍 /// 参数 flyer 类型为泛型参数T,T 约束为:需要同时实现 `Fly` trait 和 `Animal` trait fn animal_fly<T: Fly + Animal>(flyer: T) { // fly 函数来自 `Fly` trait flyer.fly(); // say 函数来自 `Animal` trait println!("{},我正在天空翱翔", flyer.say()) }
PHP 中没有泛型,PHP 8 的 Union Types 是或(“|”)的关系,而不是且的关系。
方法重载
方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。在 Java 中,可以在
class
中直接定义多个重载方法。rust 中,无法为结构体定义重载方法,但是可以借助 trait 实现。例如:
i64
的from
方法,是通过核心库的From<T>
trait 实现的
/// 给 `Bird` 声明 `from` 创建对象方法 impl Bird { fn from(name: &str) -> Self { Bird { name: name.to_string() } } } /// 通过 `From<T>` trait 重载 from 方法 /// 通过编号(u32)创建对象 impl From<u32> for Bird { fn from(num: u32) -> Self { Bird { name: format!("小{}", num) } } } /// 通过名称(String)创建对象 impl From<String> for Bird { fn from(name: String) -> Self { Bird { name } } }
这样
Bird
也有几个from
重载方法了,rust 会根据参数类型选择调用对应的方法常用 Trait
Display
打印对象信息
use std::fmt::{Display, Formatter}; struct Pig { name: String } impl Display for Pig { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "这只小猪是:{}", self.name) } } fn main() { let bird = Pig { name: "佩奇".to_string() }; println!("{}", bird); // 这只小猪是:佩奇 }
Debug
打印对象 Debug 信息
#[derive(Debug)] struct Pig { name: String } fn main() { let bird = Bird { name: "佩奇".to_string() }; println!("{:?}", bird); // Pig { name: "佩奇" } }
Clone
#[derive(Clone)] struct Sheep { name: String } fn main() { let sheep1 = Sheep { name: "Dolly".to_string() }; // 所有权发生转移,pig1 变成未定义 let sheep2 = sheep1; // 报错 println!("sheep1 是 {}", sheep1.name); // 调用 Clone Trait 的 clone 函数克隆羊,sheep2 依然存在 let sheep3 = sheep2.clone(); println!("sheep2 是 {}", sheep2.name); println!("sheep3 是 {}", sheep3.name); }
Copy
/// 实现 Copy trait 需要同时实现 Clone #[derive(Clone, Copy)] struct Int(i64); fn main() { let int1 = Int(1); // 不发生所有权转移,自动复制 let int2 = int1; println!("int1: {}", int1.0); println!("int2: {}", int2.0); }
From / Into
/// 为 Int 实现 From<i32> impl From<i32> for Int { fn from(num: i32) -> Self { Int(i64::from(num)) } } /// 为 Int 实现 Into<i32> impl Into<i32> for Int { fn into(self) -> i32 { self.0 as i32 } } fn main() { let int1 = Int::from(1_i32); let inner: i32 = int1.into(); println!("int1: {}", int1.0); println!("inner: {}", inner); }
-
PHP中trait使用方法详细介绍
2020-12-20 05:15:56说通俗点,PHP中使用trait关键字是为了解决一个类既想集成基类的属性和方法,又想拥有别的基类的方法,而trait一般情况下是和use搭配使用的。 <?php trait Drive { public $carName = 'trait'; public ... -
Personality trait characteristics of differentially gifted students
2021-06-29 21:39:44Personality trait characteristics of differentially gifted students CLASSIFYING FIRST-GRADE CHILDREN INTO READING GROUPS 189 CHISSOM, B. S., & THOMAS, J. R. Multivariate validity of the Otis-... -
PHP Laravel中的Trait使用方法
2020-10-17 12:21:17在本篇文章里小编给各位分享了关于PHP Laravel中的Trait使用方法和相关知识点,有需要的朋友们学习下。 -
PHP中用Trait封装单例模式的实现
2021-01-21 16:15:57单例模式的定义 确保某一个类只有一个实例,不能重复实例,只能它自己实例化,而且向整个系统提供这个实例。 解决的问题 ...Trait来封装单例 php从以前到现在一直都是单继承的语言,无法同时从两个基类中继 -
PHP 使用 Trait 解决 PHP 单继承问题详解
2020-10-15 09:59:10主要介绍了PHP 使用 Trait 解决 PHP 单继承问题,结合实例形式详细分析了PHP 使用 Trait 实现PHP单继承的相关操作技巧与注意事项,需要的朋友可以参考下 -
PHP的Trait机制原理与用法分析
2020-10-16 04:22:48主要介绍了PHP的Trait机制原理与用法,结合实例形式分析了PHP Trait机制的基本功能、原理及简单使用技巧,需要的朋友可以参考下 -
Rust 编程视频教程(进阶)——019 sync 和 send trait 的可扩展并发
2021-01-06 20:37:011、有两个并发概念内嵌于语言中:std::marker中的Sync和Send trait。 2、通过Send允许在线程间转移所有权 (1)Send标记trait表明类型的所有权可以在线程间传递。几乎所有的Rust类型都是Send的,但是例外:例如Rc是... -
Person authentication using finger snapping —A new biometric trait
2021-02-22 06:24:34Person authentication using finger snapping —A new biometric trait -
Mapping of Re, a gene conferring the red leaf trait in ornamental kale (Brassica oleracea L....
2020-02-17 04:20:01羽衣甘蓝红叶基因定位,任杰,刘志勇,叶色是羽衣甘蓝重要的观赏性状,羽衣甘蓝叶片因积累花青素而呈现红色。本研究利用红叶DH系'Y005-15'和白叶DH系'Y011-13-38'配制杂交组合� -
yii2-crud-trait:Yii2 带有 php 特性的简单 CRUD
2021-06-04 01:33:08Yii2 CRUD 特性 -
Laravel开发-trait-maker
2019-08-27 22:30:12Laravel开发-trait-maker 创建特征存根的artisan命令 -
trait
2019-10-02 18:56:24PHP5.4实现了一种代码复用的方法称为Trait。 Trait为了减少单继承语言的限制,能够自由地在不同层次...一个类要应用trait需要使用use关键字,一个类可以应用多个trait,只需要在use关键字后用逗号分隔多个trait。...PHP5.4实现了一种代码复用的方法称为Trait。
Trait为了减少单继承语言的限制,能够自由地在不同层次结构内独立的类中复用方法,Trait和Class组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。无法通过 trait 自身来实例化。一个类要应用trait需要使用use关键字,一个类可以应用多个trait,只需要在use关键字后用逗号分隔多个trait。
trait中也可以应用其他的trait。<?php trait a{ function getA(){ return "a"; } } trait b{ function getB(){ return "b"; } } class Test{ use a,b; } $test=new Test(); echo $test->getA()."\n"; echo $test->getB()."\n"; trait c{ use a,b; } class Test2{ use c; } $test=new Test2(); echo $test->getA()."\n"; echo $test->getB()."\n"; ?>
如果trait和class中存在相同的成员,则优先级规则为,[当前类的成员->trait的成员->继承的成员]。
<?php trait a{ function getA(){ parent::getA(); echo "a in trait\n"; } } class Base{ function getA(){ echo "a in Base\n"; } } class Test extends Base{ use a; } class Test2 extends Base{ use a; function getA(){ echo "a in Test2\n"; } } $test=new Test(); $test->getA(); $test2=new Test2(); $test2->getA(); ?>
在应用多个trait时,如果插入了同名的方法,则需要解决冲突,否则会产生致命错误。解决冲突可以使用insteadof关键字来指明使用哪一个方法。还可以通过as关键字为某个方法引入别名,as关键字还可以调整方法的访问控制。
<?php trait A { public function printLower() { echo 'a'; } public function printUpper() { echo 'A'; } } trait B { public function printLower() { echo 'b'; } public function printUpper() { echo 'B'; } public function testAccess(){ } } class Test { use A, B { B::printLower insteadof A; A::printUpper insteadof B; B::printUpper as private upper; testAccess as protected; } } $test=new Test(); $test->printLower(); $test->printUpper(); //$test->upper(); //$test->testAccess(); ?>
trait还支持抽象方法,应用trait的类需要实现抽象方法,否则会产生致命错误。trait还支持静态成员。
<?php trait a{ function lower(){ return "a"; } abstract function upper(); public static function foo(){ echo "static function foo\n"; } } class Test{ use a; function upper(){ return "A"; } } $t=new Test(); echo $t->lower()."\n"; echo $t->upper()."\n"; Test::foo(); ?>
trait中可以定义属性,但是应用trait的类不能定义与之同名的属性,否则会产生致命错误,除非属性是兼容的,即访问控制和初始默认值都相同。PHP7.0之前即使属性兼容也会有E_STRICT的提醒。
<?php trait a{ public $var="a"; public $diff1="a"; public $diff2="a"; } class Test{ use a; public $var="a"; //public $diff1="b"; //protected $diff2="a"; } ?>
-
Laravel开发-artisan-trait-maker
2019-08-28 04:33:02Laravel开发-artisan-trait-maker 创建特征存根的artisan命令 -
Laravel开发-laravel-trait-uuid
2019-08-27 23:47:54Laravel开发-laravel-trait-uuid 向雄辩模型添加UUID的大型雄辩模型特征 -
PHP Trait 引用
2021-08-09 11:27:22Trait 概述 自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。 Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内...Trait
本文作者LockieDeng,PHP工程师,致力于倒腾各种前沿技术,欢迎交流:
dengmuuming@163.com
概述
自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。
Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。官网地址:https://www.php.net/manual/zh/language.oop5.traits.php
示例 #1 Trait
Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。
<?php trait ezcReflectionReturnInfo { function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ } } class ezcReflectionMethod extends ReflectionMethod { use ezcReflectionReturnInfo; /* ... */ } class ezcReflectionFunction extends ReflectionFunction { use ezcReflectionReturnInfo; /* ... */ } ?>
优先级
从基类继承的成员会被 trait 插入的成员所覆盖。优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。
示例 #2 优先顺序示例
从基类继承的成员被插入的 SayWorld Trait 中的 MyHelloWorld 方法所覆盖。其行为 MyHelloWorld 类中定义的方法一致。优先顺序是当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法。
<?php class Base { public function sayHello() { echo 'Hello '; } } trait SayWorld { public function sayHello() { parent::sayHello(); echo 'World!'; } } class MyHelloWorld extends Base { use SayWorld; } $o = new MyHelloWorld(); $o->sayHello(); ?>
以上例程会输出:
Hello World!
示例 #3 另一个优先级顺序的例子
<?php trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } class TheWorldIsNotEnough { use HelloWorld; public function sayHello() { echo 'Hello Universe!'; } } $o = new TheWorldIsNotEnough(); $o->sayHello(); ?>
以上例程会输出:
Hello Universe!
多个 trait
通过逗号分隔,在 use 声明列出多个 trait,可以都插入到一个类中。
示例 #4 多个 trait 的用法
<?php trait Hello { public function sayHello() { echo 'Hello '; } } trait World { public function sayWorld() { echo 'World'; } } class MyHelloWorld { use Hello, World; public function sayExclamationMark() { echo '!'; } } $o = new MyHelloWorld(); $o->sayHello(); $o->sayWorld(); $o->sayExclamationMark(); ?>
以上例程会输出:
Hello World!
冲突的解决
如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。
为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。
以上方式仅允许排除掉其它方法,as 操作符可以 为某个方法引入别名。 注意,as 操作符不会对方法进行重命名,也不会影响其方法。示例 #5 冲突的解决
在本例中 Talker 使用了 trait A 和 B。由于 A 和 B 有冲突的方法,其定义了使用 trait B 中的 smallTalk 以及 trait A 中的 bigTalk。
Aliased_Talker 使用了 as 操作符来定义了 talk 来作为 B 的 bigTalk 的别名。
<?php trait A { public function smallTalk() { echo 'a'; } public function bigTalk() { echo 'A'; } } trait B { public function smallTalk() { echo 'b'; } public function bigTalk() { echo 'B'; } } class Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; } } class Aliased_Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; B::bigTalk as talk; } } $o = new Aliased_Talker(); $o->smallTalk(); $o->bigTalk(); $o->talk(); ?>
以上例程会输出:
bAB
注意: 在 PHP 7.0 之前,在类里定义和 trait 同名的属性,哪怕是完全兼容的也会抛出 E_STRICT(完全兼容的意思:具有相同的访问可见性、初始默认值)。
修改方法的访问控制
使用 as 语法还可以用来调整方法的访问控制。
示例 #6 修改方法的访问控制
<?php trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } // 修改 sayHello 的访问控制 class MyClass1 { use HelloWorld { sayHello as protected; } } // 给方法一个改变了访问控制的别名 // 原版 sayHello 的访问控制则没有发生变化 class MyClass2 { use HelloWorld { sayHello as private myPrivateHello; } } // 修改 sayHello 的访问控制 class MyClass3 { use HelloWorld { sayHello as public; } } $o = new MyClass3(); $o->sayHello(); ?>
以上例程会输出:
Hello World!
从 trait 来组成 trait
正如 class 能够使用 trait 一样,其它 trait 也能够使用 trait。在 trait 定义时通过使用一个或多个 trait,能够组合其它 trait 中的部分或全部成员。
示例 #7 从 trait 来组成 trait
<?php trait Hello { public function sayHello() { echo 'Hello '; } } trait World { public function sayWorld() { echo 'World!'; } } trait HelloWorld { use Hello, World; } class MyHelloWorld { use HelloWorld; } $o = new MyHelloWorld(); $o->sayHello(); $o->sayWorld(); ?>
以上例程会输出:
Hello World!
Trait 的抽象成员
为了对使用的类施加强制要求,trait 支持抽象方法的使用。
警告
一个可继承实体类,可以通过定义同名非抽象方法来满足要求;方法的签名可以不同。示例 #8 表示通过抽象方法来进行强制要求
<?php trait Hello { public function sayHelloWorld() { echo 'Hello'.$this->getWorld(); } abstract public function getWorld(); } class MyHelloWorld { private $world; use Hello; public function getWorld() { return $this->world; } public function setWorld($val) { $this->world = $val; } } $o = new MyHelloWorld(); $o->setWorld('你好'); $o->getWorld(); $o->sayHelloWorld(); ?>
以上例程会输出:
Hello你好
Trait 的静态成员
Traits 可以被静态成员静态方法定义。
示例 #9 静态变量
<?php trait Counter { public function inc() { static $c = 0; $c = $c + 1; echo "$c\n"; } } class C1 { use Counter; } class C2 { use Counter; } $o = new C1(); $o->inc(); // echo 1 $p = new C2(); $p->inc(); // echo 1 ?>
示例 #10 静态方法
<?php trait StaticExample { public static function doSomething() { return 'Doing something'; } } class Example { use StaticExample; } echo(Example::doSomething()); // echo Doing something ?>
属性
Trait 同样可以定义属性。
示例 #11 定义属性
<?php trait PropertiesTrait { public $x = 1; } class PropertiesExample { use PropertiesTrait; } $example = new PropertiesExample; echo( $example->x ); // echo 1 ?>
Trait 定义了一个属性后,类就不能定义同样名称的属性,否则会产生 fatal error。 有种情况例外:属性是兼容的(同样的访问可见度、初始默认值)。 在 PHP 7.0 之前,属性是兼容的,则会有 E_STRICT 的提醒。
<?php trait PropertiesTrait { public $same = true; public $different = false; } class PropertiesExample { use PropertiesTrait; public $same = true; // PHP 7.0.0 后没问题,之前版本是 E_STRICT 提醒 public $different = true; // 致命错误 } ?>
-
Laravel开发-eloquent-model-trait
2019-08-27 13:47:02Laravel开发-eloquent-model-trait 为雄辩模型添加有用的方法