18

示例#2 来自 PHP 手册http://php.net/manual/en/language.oop5.traits.php状态

<?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();
?>

这是正确的代码,但在这种情况下使用是不安全的parent::。假设我编写了自己的“hello world”类,它不继承任何其他类:

<?php
class MyOwnHelloWorld
{
    use SayWorld;
}
?>

sayHello()在我调用该方法之前,此代码不会产生任何错误。这是不好的。

另一方面,如果 trait 需要使用某种方法,我可以将此方法编写为 abstract,这很好,因为它可以确保在编译时正确使用 trait。但这不适用于父类:

<?php
trait SayWorld
{
    public function sayHelloWorld()
    {
        $this->sayHello();
        echo 'World!';
    }

    public abstract function sayHello(); // compile-time safety

}

所以我的问题是:有没有办法确保(在编译时,而不是在运行时)使用某种特征的类将具有parent::sayHello()方法?

4

4 回答 4

4

您可以检查 $this 是否扩展了特定的类或实现了特定的接口:

interface SayHelloInterface {
    public function sayHello();
}

trait SayWorldTrait {
    public function sayHello() {
        if (!in_array('SayHello', class_parents($this))) {
            throw new \LogicException('SayWorldTrait may be used only in classes that extends SayHello.');
        }
        if (!$this instanceof SayHelloInterface) {
            throw new \LogicException('SayWorldTrait may be used only in classes that implements SayHelloInterface.');
        }
        parent::sayHello();
        echo 'World!';
    }
}

class SayHello {
    public function sayHello() {
        echo 'Hello ';
    }
}

class First extends SayHello {
    use SayWorldTrait;
}

class Second implements SayHelloInterface {
    use SayWorldTrait;
}

try {
    $test = new First();
    $test->sayHello(); // throws logic exception because the First class does not implements SayHelloInterface
} catch(\Exception $e) {
    echo $e->getMessage();
}

try {
    $test = new Second();
    $test->sayHello(); // throws logic exception because the Second class does not extends SayHello
} catch(\Exception $e) {
    echo $e->getMessage();
}
于 2015-04-27T16:26:28.880 回答
4

不,那里没有。其实这个例子很糟糕,因为引入trait的目的是在不依赖继承的情况下,给多个类引入相同的功能,而且使用parent不仅要求类有父类,还需要有特定的方法。

附带说明parent一下,在编译时不会检查调用,您可以定义简单的类,该类不会在其方法中使用父调用扩展任何内容,它会一直工作,直到调用其中一个方法。

于 2012-10-21T22:14:18.903 回答
1

PHP 编译阶段仅创建字节码。其他一切都是在运行时完成的,包括多态决策。因此,如下代码编译:

class A {}
class B extends A {
    public function __construct() {
        parent::__construct();
    }
}

运行时会爆炸:

$b = new B;

因此,您严格不能parent在编译时进行检查。您能做的最好的事情就是尽早将其推迟到运行时。正如其他答案所示,可以在 trait 方法中使用instanceof. 当我知道 trait 方法需要特定契约时,我个人更喜欢使用类型提示。

所有这些都归结为特征的基本目的:编译时复制和粘贴。而已。Traits 对合同一无所知。对多态一无所知。它们只是提供了一种重用机制。当与接口结合使用时,它们非常强大,虽然有些冗长。

事实上,关于特质的第一次学术讨论揭示了特质意味着“纯粹”的想法,因为它们对周围的对象一无所知:

trait 本质上是一组纯方法,它们充当类的构建块,是代码重用的基本单元。在这个模型中,类由一组特征组成,通过指定将特征连接在一起并访问必要状态的粘合代码。

“如果特质不邪恶,它们会很有趣”很好地总结了要点、陷阱和选项。我不会分享作者对特征的刻薄:我会在有意义的时候使用它们,并且总是与interface. 不幸的是,PHP 文档中的示例鼓励了不良行为。

于 2017-01-24T21:00:46.713 回答
0

我认为有一种完全没有特征的方法:

class BaseClass 
{
    public function sayHello() {
            echo 'Hello ';
    }
}

class SayWorld
{
    protected $parent = null;

    function __construct(BaseClass $base) {
        $this->parent = $base;
    }

    public function sayHelloWorld()
    {
        $this->parent->sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    protected $SayWorld = null;

    function __construct() {
        $this->SayWorld = new SayWorld($this);
    }

    public function __call ( string $name , array $arguments ) {
        if(method_exists($this->SayWorld, $name)) {
            $this->SayWorld->$name();
        }
    }
}
于 2015-02-28T16:41:03.567 回答