PHP中的特征 - 任何现实世界的例子/最佳实践?[已关闭]

2022-08-30 06:41:21

Traits是PHP 5.4最大的新增功能之一。我知道语法并理解特征背后的想法,比如水平代码重用常见的东西,如日志记录,安全性,缓存等。

但是,我仍然不知道如何在我的项目中利用这些特征。

是否有任何开源项目已经使用特征?有没有关于如何使用特征构建架构的好文章/阅读材料?


答案 1

我想人们必须研究具有特征的语言一段时间,才能学习公认的良好/最佳实践。我目前对 Trait 的看法是,您应该只将它们用于必须在共享相同功能的其他类中复制的代码。

记录器特征的示例:

interface Logger
{
    public function log($message, $level);    
}

class DemoLogger implements Logger
{
    public function log($message, $level)
    {
        echo "Logged message: $message with level $level", PHP_EOL; 
    }
}

trait Loggable // implements Logger
{
    protected $logger;
    public function setLogger(Logger $logger)
    {
        $this->logger = $logger;
    }
    public function log($message, $level)
    {
        $this->logger->log($message, $level);
    }
}

class Foo implements Logger
{
    use Loggable;
}

然后你做(演示)

$foo = new Foo;
$foo->setLogger(new DemoLogger);
$foo->log('It works', 1);

我想在使用特征时要考虑的重要一点是,它们实际上只是复制到类中的代码片段。这很容易导致冲突,例如,当您尝试更改方法的可见性时,例如

trait T {
    protected function foo() {}
}
class A { 
    public function foo() {}
}
class B extends A
{
    use T;
}

上述情况将导致错误(演示)。同样,在 trait 中声明的任何方法,如果已经在 using 类中声明,则不会复制到该类中,例如

trait T {
    public function foo() {
    return 1;
}
}
class A { 
    use T;
    public function foo() {
    return 2;
}
}

$a = new A;
echo $a->foo();

将打印 2(演示)。这些是你想要避免的事情,因为它们会使错误难以找到。您还需要避免将事物放入对使用它的类的属性或方法进行操作的特征中,例如

class A
{
    use T;
    protected $prop = 1;
    protected function getProp() {
        return $this->prop;
    }
}

trait T
{
    public function foo()
    {
        return $this->getProp();
    }
}

$a = new A;
echo $a->foo();

工作(演示),但现在这个特征与A紧密耦合,水平重用的整个想法都丢失了。

当您遵循接口隔离原则时,您将拥有许多小类和接口。这使得Traits 成为你提到的事物的理想候选者,例如,交叉关注点,但不是组合对象(在结构意义上)。在上面的记录器示例中,特征是完全隔离的。它对具体类没有依赖关系。

我们可以使用聚合/组合(如本页其他地方所示)来实现相同的结果类,但是使用聚合/组合的缺点是,我们必须手动将代理/委托器方法添加到每个类中,然后应该能够记录。Traits通过允许我将样板保存在一个地方并在需要时有选择地应用它来很好地解决了这个问题。

注意:鉴于特征在PHP中是一个新概念,上面表达的所有观点都可能发生变化。我还没有太多的时间来评估这个概念。但我希望它足够好,可以给你一些思考的东西。


答案 2

我个人的观点是,在编写干净的代码时,实际上很少应用特征。

与其使用特征将代码破解到类中,不如通过构造函数或 setter 传入依赖项:

class ClassName {
    protected $logger;

    public function __construct(LoggerInterface $logger) {
        $this->logger = $logger;
    }
    // or
    public function setLogger(LoggerInterface $logger) {
        $this->logger = $logger;
    }
}

我发现这比使用特征更好的主要原因是,通过删除与特征的硬耦合,您的代码更加灵活。例如,您现在可以简单地传递不同的记录器类。这使您的代码可重用且可测试。


推荐