PHP 核心特性 - Trait(Life)

心已赠人 2023-03-01 11:49 22阅读 0赞

为什么 PHP 会引入 Trait ? 我们先来看看软件开发中的两种常用代码复用模式,继承组合

  • 继承:强调 父类与子类 的关系,即子类是父类的一个特殊类型;
  • 组合:强调 整体与局部 的关系,侧重的一种需要的关系;

软件开发中有一条原则,叫做组合优于继承。这是因为从耦合度来看,继承要高于组合。继承关系中,子类与父类保持着高度的依赖关系,加上 PHP 不支持多继承,为了避免重写编写代码,很多功能都被统一封装到父类中。这样做有两个坏处:一是随着继承的层数和子类的增加,代码复杂度不断增加,大量的方法都将面临着重写;二是这些功能对于一些子类来说可能是不必要的,破坏了代码的封装性。

Trait 的提出弥补了 PHP 对组合支持的不足,一个 Trait 就相当于一个模块,不同的 Trait 以组合的方式注入到类中。我们以 Laravel 的控制器为例,来介绍下继承和组合是如何在具体的场景中使用的。

首先,底层的代码应当多使用组合。Laravel 的底层控制器只继承了一个简单的控制器 Illuminate\Routing\Controller,结构相对稳定。同时,控制器使用了不同的 Trait 来组织代码,避免了对象的臃肿,极大程度的保持了架构的灵活性。

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
  4. use Illuminate\Foundation\Bus\DispatchesJobs;
  5. use Illuminate\Foundation\Validation\ValidatesRequests;
  6. use Illuminate\Routing\Controller as BaseController;
  7. class Controller extends BaseController
  8. {
  9. use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
  10. }

具体的业务逻辑或顶层代码应当多使用继承,这样能够大大提高的开发效率

  1. <?php
  2. use App\Http\Controllers\Controller;
  3. class UserController extends Controller
  4. {
  5. }

以上就是继承和组合的简单介绍。接下来看看 Trait 的具体使用。

使用规范

Symfony 编码规范建议在每个 Trait 之后添加 Trait 关键字。

  1. namespace Symfony\Contracts\Translation;
  2. trait TranslatorTrait {
  3. }

PSR-12 规范建议在每个 Trait 使用一个 use 语句来声明,同时 Trait 与类的其他成员需要保持一行空行。

  1. class ClassName
  2. {
  3. use FirstTrait;
  4. use SecondTrait;
  5. use ThirdTrait;
  6. public $a;
  7. }

点击观看—>【粉丝福利】30G-PHP进阶资料,拿到你也能30K,免费领取

成员

Trait 中可包含属性、方法 与 抽象方法,这三者的结合既可以复用代码,也可以对代码的使用作出一些约定,例如 Laravel 中的自动维护 slug 字段

  1. <?php
  2. namespace App\Traits;
  3. use Illuminate\Support\Str;
  4. trait HasSlug
  5. {
  6. public static function bootSluggable()
  7. {
  8. static::saving(function ($model) {
  9. $model->slug = Str::slug(
  10. $model->getAttribute( $model->sluggable() )
  11. );
  12. });
  13. }
  14. /**
  15. * Slug 字段
  16. *
  17. * @return string
  18. */
  19. abstract public function sluggable(): string;
  20. }

Trait 中也可以包括静态属性和静态方法,以下是一个单例模式的简单封装。

  1. trait Singleton
  2. {
  3. private static $instance;
  4. public static function getInstance() {
  5. if (!(self::$instance instanceof self)) {
  6. self::$instance = new self;
  7. }
  8. return self::$instance;
  9. }
  10. }

每个 Trait 中可以包含其他 Trait,进一步提高了代码的灵活性

  1. <?php
  2. trait Hello
  3. {
  4. function sayHello() {
  5. echo "Hello";
  6. }
  7. }
  8. trait World
  9. {
  10. function sayWorld() {
  11. echo "World";
  12. }
  13. }
  14. class MyWorld
  15. {
  16. use Hello;
  17. use World;
  18. }

Trait 与类的冲突处理

当存在同名方法时,当前类的方法会覆盖 Trait 中的方法,而 Trait 中的方法会覆盖父类的方法。

  1. <?php
  2. // 父类,优先级最低
  3. class Base {
  4. public function sayHello() {
  5. echo 'Hello ';
  6. }
  7. }
  8. // Trait 优先级大于父类
  9. trait SayWorld {
  10. public function sayHello() {
  11. parent::sayHello();
  12. echo 'World!';
  13. }
  14. }
  15. // 当前类,优先级最高
  16. class MyHelloWorld extends Base {
  17. use SayWorld;
  18. }
  19. $o = new MyHelloWorld();
  20. $o->sayHello(); // Hello, World

当存在同名属性时,类的属性必须Trait 的属性兼容(相同的访问性、相同的初始值),否则会报致命错误

  1. <?php
  2. trait Foo {
  3. public $same = true;
  4. public $different = false;
  5. }
  6. class Bar {
  7. use Foo;
  8. public $same = true; // 合法
  9. public $different = true; // fatal error
  10. }

Trait 与 Trait 的冲突处理

当一个类包含多个 Trait 时,不同 Trait 之间可能会存在属性和方法的冲突。

当存在相同属性时,属性必须兼容,跟 Trait 与类的冲突处理类似

  1. Trait A {
  2. public $a = 'foo';
  3. }
  4. Trait B {
  5. public $a = 'foo';
  6. }
  7. class Foo
  8. {
  9. use A;
  10. use B;
  11. }

当存在方法冲突时,需要使用 insteadof 来手动处理冲突,否则会报致命错误

  1. <?php
  2. Trait A {
  3. public function foo()
  4. {
  5. return "A foo";
  6. }
  7. }
  8. Trait B {
  9. public function foo()
  10. {
  11. return "B foo";
  12. }
  13. }
  14. class Bar {
  15. use A, B {
  16. B::foo insteadof A; // 用 B 的 foo 方法来代替 A
  17. }
  18. }
  19. $bar = new Bar();
  20. echo $bar->foo(); // B foo

这时候如果想要保留 A 的 foo 方法,可以用 as 定义别名来进行调用。注意,起别名仅仅代表可以用别名来调用该方法,仍然需要用 insteadof 处理冲突

  1. class Bar {
  2. use A, B {
  3. B::foo insteadof A; // 用 B 的 foo 方法来代替 A
  4. A::foo as aFoo; // A 的 foo 方法用 aFoo 来调用
  5. }
  6. }
  7. $bar = new Bar();
  8. echo $bar->aFoo(); // A foo

as 关键字还可以用来更改方法法的访问控制

  1. <?php
  2. Trait A {
  3. public function foo()
  4. {
  5. return "A foo";
  6. }
  7. }
  8. class Bar {
  9. use A {
  10. A::foo as private;
  11. }
  12. }
  13. $bar = new Bar();
  14. echo $bar->foo(); // Fatal error: Uncaught Error: Call to private method Bar::foo()

这两者可以结合起来用,这时候原有方法的访问控制就不会受到影响

  1. <?php
  2. Trait A {
  3. public function foo()
  4. {
  5. return "A foo";
  6. }
  7. }
  8. class Bar {
  9. use A {
  10. A::foo as private aFoo;
  11. }
  12. }
  13. $bar = new Bar();
  14. echo $bar->foo(); // A foo,照常调用
  15. echo $bar->aFoo(); // 被设置成私有方法,因此报错。Fatal error: Uncaught Error: Call to private method

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家(点击此处加入php高级交流群一起学习交流,11年架构师带你解读年薪50万面试通关秘籍。)

如果喜欢我的文章,想与一群资深开发者一起交流学习的话,获取更多相关大厂面试咨询和指导,欢迎加入我的学习交流群556094961

发表评论

表情:
评论列表 (有 0 条评论,22人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Rust核心特性—所有权机制

    简介 Rust的核心所有权机制是一种在编译时静态检查的机制,用于管理内存分配和释放的问题。这一机制保证了程序的内存安全性,是Rust的一大特点。 解释 在Rust

    相关 CSS3核心特性介绍

    以下介绍四个CSS3常用的特性:圆角处理、变形(偏移、旋转等)、媒体查询、弹性布局。 幸福的生活来之不易,在没有CSS3的日子里,要实现这些功能简直就是前端开发者的噩梦。更值

    相关 Spring 的核心特性

    Spring 框架现在已经是一个庞大的生态,在入门 Java 没多久我就尝试过读 Spring 的源码,按能抽出整块时间读源码去算的话,有 3 次;《Spring 源码深度解析

    相关 php核心知识点

    Php:脚本语言,网站建设,服务器端运行PHP定义:一种服务器端的 HTML 脚本/编程语言,是一种简单的、面向对象的、解释型的、健壮的、安全的、性能非常之高的、独立于架构的、

    相关 Spring4.0_核心特性

    条件化配置:     说明:可以在运行时判断这个配置是该被运用,还是该被忽略。     应用:         @ConditionalOnClass : classpa

    相关 Spring3.1_核心特性

    开始支持基于Profile的配置:     说明: Profile是一种条件化配置,基于运行时激活的Profile,会使用或者忽略不同的Bean或配置类。     应用:@