美文网首页
PHP设计模式篇(六) Trait特性——类方法的组合模式

PHP设计模式篇(六) Trait特性——类方法的组合模式

作者: 四月_兔 | 来源:发表于2023-08-11 18:01 被阅读0次

原文来自 我的分享站 点击进入 原文

一、什么是Trait

我们知道PHP类是不能多继承的,Trait就是类似于多继承的一种代码复用机制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。

实际上,这里说Trait是多继承不太恰当,虽然它能达到多继承的效果,复用多个类的方法,但是无法描述多继承中的对象归属关系(即is-a关系)。更确切的来说,trait提供的是一种方法的组合功能,类似于接口类,能反映类与方法的包含关系(has-a关系),只是接口类无法实现方法,而trait可以。

接下来我们通过一个场景来看看trait如何使用,能带来什么好处。

请看如下场景:如果有若干个类需要使用单例模式,为了复用实例化对象相关的方法代码,我们能想到的做法是在父类实现单例功能,子类继承并实现具体的其他功能。

然而事情没有那么简单,假如现在业务上有3个需求,我们需要写3个方法(简称为方法1~3)来分别满足这3个需求。并且上述的子类A需要使用方法1和3,子类B使用方法1和2,子类C使用方法2和3,而且这三个方法业务上毫无关联(也就是分属3个不同业务的需求),因此将三个方法放到父类中实现虽然满足复用性,却在逻辑上不合理,此时就需要Trait解决我们的痛点。

我们可以将 方法1~3 分别放到 Trait1~3,然后子类1使用Trait1和Trait3,子类2使用 Trait1 和 Trait2。也就是说一个业务类可以随意的组合使用哪几个 Trait,从而让这个业务类具备哪些几个功能。

下面看个例子:

trait Singleton
{
    private static $instance;

    public static function getInstance() {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self;
        }
        return self::$instance;
    }
}

class DbReader extends ArrayObject
{
    use Singleton;
}

class  FileReader
{
    use Singleton;
}

Trait 与抽象类类似,可定义抽象方法和正常方法,但自身无法实例化。通过在一个类中 use 一个Trait,该Trait内的方法和属性就会被水平的注入该类中。

所谓的水平的注入,意思是如果一个Trait定义了某些属性或方法,那么use这个Trait的类A也可以使用这些属性/方法,即便这些属性/方法是私有的,因为Trait中的所有属性和方法都被注入到了类A,这些属性和方法就变成了类A的属性和方法。而对于继承而言,子类是不能使用父类的私有属性/方法的。因此这一点也体现了Trait特性只能说表面类似于多继承,实际上不算是多继承,而是一种组合模式。

二、Trait特性

为了方便说明,下面内容中我们将 use Trait 语句所在的类称为使用类

0、Trait自身无法实例化,而且Trait的静态方法或属性不能直接由Trait自身调用,只能由其使用类调用。

1、私有属性/方法水平注入,被注入trait的类(即使用类)可访问trait的私有成员,反之trait也可以访问被注入trait的类的私有成员,当然后者的情况很少,因为一般来说trait本身是不知道它的实现类中有些什么属性。

例如:

<?php
trait Message
{
    function alert() {
        echo $this->message;
    }
}

class Messenger
{
    use Message;
    private $message = "This is a message";
}
 

2、一个类可以use多个trait,一个trait可以也可以use多个trait组合成一个新trait。

<?php
trait Hello
{
    function sayHello() {
        echo "Hello";
    }
}

trait World
{
    function sayWorld() {
        echo "World";
    }
}

trait HelloWorld
{
    use Hello, World;
}

class MyWorld
{
    use HelloWorld;
}

$world = new MyWorld();
echo $world->sayHello() . " " . $world->sayWorld(); //Hello World
 

3、对于方法而言:trait 的方法覆盖从父类继承的方法(不难理解,因为trait和被注入trait的类是同级的),当前类定义的方法覆盖 trait 的方法。

例如:

<?php
trait Hello
{
    function sayHello() {
        return "Hello";
    }

    function sayWorld() {
        return "Trait World";
    }

    function sayHelloWorld() {
        echo $this->sayHello() . " " . $this->sayWorld();
    }

    function sayBaseWorld() {
        echo $this->sayHello() . " " . parent::sayWorld();
    }
}

class Base
{
    function sayWorld(){
        return "Base World";
    }
}

class HelloWorld extends Base
{
    use Hello;
    function sayWorld() {
        return "World";
    }
}

$h =  new HelloWorld();
$h->sayHelloWorld(); // Hello World
$h->sayBaseWorld(); // Hello Base World
 

对于非静态属性而言:如果一个 trait 定义了一个属性,那么一个使用类不能定义一个具有相同名称的属性,除非它具有相同的可见性和初始值,否则会发出致命错误。

<?php
trait PropertiesTrait {
    public $same = true;
    public $different = false;
}

class PropertiesExample {
    use PropertiesTrait;
    public $same = true;
    public $different = true; // Fatal error
}
?>

对于静态属性而言:对于trait中的某个静态属性,多个使用类的该静态属性是相互独立的(而对于继承而言,父类中的静态属性在所有子类中是共享的,一个子类对其修改,另一个子类访问到的是修改后的值)。

<?php
trait Singleton
{
    protected static $name;

    public static function setName($name){
      self::$name = $name;
    }

    public static function getName(){
      return self::$name;
    }
}

class DbReader
{
    use Singleton;
}

class  FileReader
{
    use Singleton;
}

DbReader::setName('zbp');
FileReader::setName('abc');
var_dump(DbReader::getName());  // zbp
var_dump(FileReader::getName());  // abc
var_dump(Singleton::getName());   // 报Deprecated并返回null
 

4、多个 traits 使用了相同的方法名将抛出一个致命错误,需要insteadof关键字解决冲突。

<?php
trait Game
{
    function play() {
        echo "Playing a game";
    }
}

trait Music
{
    function play() {
        echo "Playing music";
    }
}

class Player
{
    use Game, Music {
        Music::play insteadof Game;
    }
}

$player = new Player();
$player->play(); //玩音乐

$player = new Player();
$player->play();

另一种方式是保留两个方法,为其中给一个方法起别名。

<?php
class Player
{
    use Game, Music {
        Game::play as gamePlay;
        Music::play insteadof Game;
    }
}

$player = new Player();
$player->play(); //玩音乐
$player->gamePlay(); //玩游戏

5、trait可以定义抽象方法来强制要求使用类必须实现这些抽象方法。

6、PHP 的 Trait 可以拥有构造器(__construct(),如果使用类也定义了构造方法,则实用类的构造方法会覆盖Trait的构造方法,且无法通过parent::__construct的方式保留),但是必须将其声明为 public,如果声明 protected 或 private 将会报错。在 Trait 中使用构造器时应当谨慎,因为经常会导致使用类出现意料之外的冲突。

7、可以使用as关键字改变trait中的方法的可见性(可以在使用类中将trait的方法的权限改大或者改小)。

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}
?>

相关文章

  • ThinkPHP设计模式与Trait技术

    阅读原文 设计模式 单例模式 工厂模式 对象注册树 示例 运行 Trait技术 自 PHP 5.4.0 起,PHP...

  • Python面向对象

    目录:一、类与对象二、访问限制三、类属性四、类方法五、静态方法六、继承七、组合八、单例模式(详细阐述见设计模式) ...

  • PHP基础( php7新特性 )

    严格模式( 函数 ) 匿名类 trait 特性 上传文件参考地址 : https://www.cnblogs...

  • 设计模式之类与类的关系-继承,组合,聚合,实现,依赖,关联

    今天看设计模式,有个组合模式,感觉不太像自己想象中的组合,其实这是概念错误。 组合模式是一种设计模式 组合是类与类...

  • PHP Trait实现Singleton单例模式

    trait是从PHP 5.4开始就有的语法特性,与Mixin和Behaviors模式有相似之处。triat 突破了...

  • 设计模式总结笔记

    设计模式笔记(无图) 继承与组合 应尽量把相同的特性提取到超类,把各自的特性抽象成接口。具体实现可以使用组合。如果...

  • 2019-01-16

    1.trait 关键字 实现多继承 注:trait里面也可以定义属性 2.接口类和抽象类以及设计模式 (1)接口类...

  • PHP-浅谈单例模式和工厂模式

    PHP中常用的设计模式有单例模式、工厂模式(简单工厂模式、工厂方法模式和抽象工厂方法模式)、适配模式、策略模式。 ...

  • 设计模式开篇 2018-07-31

    总体来说设计模式分为三大类六大原则: 设计模式分类: 创建型模式(五种):工厂方法模式、抽象工厂模式、单例模式、建...

  • 面试 (七) : 其他篇 -- 设计模式

    常用的设计模式 • 单例模式 • 组合模式 • 观察者模式 • 代理模式 • 享元模式 • 工厂方法模式 • 抽象...

网友评论

      本文标题:PHP设计模式篇(六) Trait特性——类方法的组合模式

      本文链接:https://www.haomeiwen.com/subject/breopdtx.html