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

Posted

技术标签:

【中文标题】PHP 的特征——任何现实世界的例子/最佳实践? [关闭]【英文标题】:Traits in PHP – any real world examples/best practices? [closed] 【发布时间】:2011-12-15 02:50:28 【问题描述】:

Traits 是 php 5.4 的最大新增功能之一。我知道语法并理解特征背后的想法,例如日志、安全、缓存等常见内容的横向代码重用。

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

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

【问题讨论】:

这是我的意见:blog post 关于我在该主题上写的主题。 TL;DR:基本上,我担心虽然它们功能强大并且可以用于良好的用途,但我们将看到的大多数用途将是完全的反模式,并且造成的痛苦远远超过它们解决的问题...... 看看scala standard library,你会发现很多有用的特质例子。 【参考方案1】:

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

Logger 特征示例:

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;

然后你做 (demo)

$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;

以上将导致错误 (demo)。同样,在 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 (demo)。这些是您要避免的事情,因为它们使错误难以发现。您还需要避免将事物放入对使用它的类的属性或方法进行操作的特征中,例如

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

工作 (demo) 但现在 trait 与 A 紧密耦合,并且水平重用的整个想法都丢失了。

当您关注Interface Segregation Principle 时,您将拥有许多小类和接口。这使得 Traits 成为您提到的事物的理想候选者,例如crosscutting concerns,但不是组合对象(在结构意义上)。在我们上面的 Logger 示例中,特征是完全隔离的。它不依赖于具体的类。

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

注意:鉴于特征是 PHP 中的一个新概念,上述所有观点可能会发生变化。我自己还没有太多时间来评估这个概念。但我希望它足以让你思考。

【讨论】:

这是一个有趣的用例:使用定义契约的接口,使用特征来满足契约。不错。 我喜欢这种真正的程序员,他们提出了一个真正的工作示例,每个示例都带有简短的描述。谢谢 如果有人改用抽象类怎么办?替换接口和特征,可以创建一个抽象类。此外,如果应用程序需要接口,抽象类也可以像 trait 那样实现接口并定义方法。那么你能解释一下为什么我们仍然需要特质吗? @sumanchalki 抽象类遵循继承规则。如果您需要一个实现 Loggable 和 Cacheable 的类怎么办?您需要该类来扩展 AbstractLogger ,然后再扩展 AbstractCache 。但这意味着所有 Loggable 都是缓存。那是你不想要的耦合。它限制了重用并弄乱了您的继承图。 我认为演示链接已失效【参考方案2】:

我个人的看法是,在编写干净的代码时,trait 的应用实际上很少。

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

class ClassName 
    protected $logger;

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

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

【讨论】:

使用特征,你也可以使用另一个记录器类,对吧?只需编辑特征,所有使用特征的类都会更新。如果我错了,请纠正我 @rickchristie 当然,你可以这样做。但是您需要编辑特征的源代码。因此,您将为使用它的每个类更改它,而不仅仅是您想要不同记录器的特定类。如果你想使用同一个类但有两个不同的记录器怎么办?或者,如果您想在测试时通过模拟记录器?你不能,如果你使用特征,你可以,如果你使用依赖注入。 我明白你的意思,我也在思考特质是否值得。我的意思是,在像 Symfony 2 这样的现代框架中,你到处都有依赖注入,这在大多数情况下似乎优于特征。目前,我认为特征并不比“编译器辅助复制和粘贴”更多。 ;) 目前我认为特征并不比“编译器辅助复制和粘贴”更多。 ;):@Max:这正是特征的设计初衷,所以这是完全正确的。它使它更“可维护”,因为只有一个定义,但它基本上只是 c&p... NikiC 没有抓住重点:使用 trait 并不能阻止使用依赖注入。在这种情况下,一个 trait 只会让每个实现日志记录的类不必重复 setLogger() 方法和 $logger 属性的创建。该特征将提供他们。 setLogger() 将像示例一样在 LoggerInterface 上键入提示,以便可以传入任何类型的记录器。这个想法类似于下面 Gordon 的答案(只是看起来他是在 Logger 超类而不是 Logger 接口上的类型提示)。【参考方案3】:

:) 我不喜欢理论化和辩论应该如何处理某事。在这种情况下特征。我会向你展示我认为特质有用的地方,你可以从中学习,也可以忽略它。

特征 - 它们非常适合应用策略。简而言之,当您希望以不同方式处理(过滤、排序等)相同的数据时,策略设计模式非常有用。

例如,您有一个产品列表,您希望根据某些标准(品牌、规格等)或按不同方式(价格、标签等)进行排序。您可以创建一个排序特征,其中包含针对不同排序类型(数字、字符串、日期等)的不同函数。然后,您不仅可以在您的产品类中使用此特征(如示例中所示),还可以在需要类似策略的其他类中使用此特征(对某些数据应用数字排序等)。

试试看:

<?php
trait SortStrategy 
    private $sort_field = null;
    private function string_asc($item1, $item2) 
        return strnatcmp($item1[$this->sort_field], $item2[$this->sort_field]);
    
    private function string_desc($item1, $item2) 
        return strnatcmp($item2[$this->sort_field], $item1[$this->sort_field]);
    
    private function num_asc($item1, $item2) 
        if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
        return ($item1[$this->sort_field] < $item2[$this->sort_field] ? -1 : 1 );
    
    private function num_desc($item1, $item2) 
        if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
        return ($item1[$this->sort_field] > $item2[$this->sort_field] ? -1 : 1 );
    
    private function date_asc($item1, $item2) 
        $date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
        $date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
        if ($date1 == $date2) return 0;
        return ($date1 < $date2 ? -1 : 1 );
    
    private function date_desc($item1, $item2) 
        $date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
        $date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
        if ($date1 == $date2) return 0;
        return ($date1 > $date2 ? -1 : 1 );
    


class Product 
    public $data = array();

    use SortStrategy;

    public function get() 
        // do something to get the data, for this ex. I just included an array
        $this->data = array(
            101222 => array('label' => 'Awesome product', 'price' => 10.50, 'date_added' => '2012-02-01'),
            101232 => array('label' => 'Not so awesome product', 'price' => 5.20, 'date_added' => '2012-03-20'),
            101241 => array('label' => 'Pretty neat product', 'price' => 9.65, 'date_added' => '2012-04-15'),
            101256 => array('label' => 'Freakishly cool product', 'price' => 12.55, 'date_added' => '2012-01-11'),
            101219 => array('label' => 'Meh product', 'price' => 3.69, 'date_added' => '2012-06-11'),
        );
    

    public function sort_by($by = 'price', $type = 'asc') 
        if (!preg_match('/^(asc|desc)$/', $type)) $type = 'asc';
        switch ($by) 
            case 'name':
                $this->sort_field = 'label';
                uasort($this->data, array('Product', 'string_'.$type));
            break;
            case 'date':
                $this->sort_field = 'date_added';
                uasort($this->data, array('Product', 'date_'.$type));
            break;
            default:
                $this->sort_field = 'price';
                uasort($this->data, array('Product', 'num_'.$type));
        
    


$product = new Product();
$product->get();
$product->sort_by('name');
echo '<pre>'.print_r($product->data, true).'</pre>';
?>

作为结束语,我考虑了诸如配件之类的特征(我可以用它来更改我的数据)。类似的方法和属性可以从我的类中删除并放在一个地方,以便于维护、更短和更清晰的代码。

【讨论】:

虽然这可以保持公共界面整洁,但内部界面可能会变得非常复杂,特别是如果您将其扩展到其他事物,例如颜色。我认为简单的函数或静态方法在这里更好。 我喜欢strategies这个词。【参考方案4】:

我对 Traits 感到很兴奋,因为他们在为 Magento 电子商务平台开发扩展程序时解决了 common issue。当扩展通过扩展核心类(比如用户模型)向核心类添加功能时,就会出现问题。这是通过指向 Zend 自动加载器(通过 XML 配置文件)使用扩展中的用户模型,并让新模型扩展核心模型来完成的。 (example) 但是如果两个扩展覆盖同一个模型呢?你得到一个“竞争条件”并且只加载了一个。

现在的解决方案是编辑扩展,以便一个扩展链中另一个的模型覆盖类,然后设置扩展配置以正确的顺序加载它们,以便继承链工作。

此系统经常导致错误,并且在安装新扩展程序时需要检查冲突并编辑扩展程序。这很痛苦,并且会破坏升级过程。

我认为使用 Traits 将是完成同样事情的好方法,而无需这种烦人的模型覆盖“竞争条件”。当然,如果多个 Traits 实现具有相同名称的方法,仍然可能存在冲突,但我想像一个简单的命名空间约定这样的东西可以在很大程度上解决这个问题。

TL;DR 我认为 Traits 可用于为 Magento 等大型 PHP 软件包创建扩展/模块/插件。

【讨论】:

【参考方案5】:

你可以有这样一个只读对象的特征:

  trait ReadOnly  
      protected $readonly = false;

      public function setReadonly($value) $this->readonly = (bool)$value; 
      public function getReadonly($value) return $this->readonly; 
  

您可以检测是否使用了该特征并确定是否应该将该对象写入数据库、文件等中。

【讨论】:

所以use这个特征的类然后会调用if($this -&gt; getReadonly($value));但是如果你没有use 这个特性,这会产生一个错误。因此这个例子是有缺陷的。 嗯,你需要先检查特征是否在使用中。如果在对象上定义了 ReadOnly 特征,则可以检查它是否为只读。 我在gist.github.com/gooh/4960073 中为这种特征做了一个通用的概念证明 您应该为此目的为 ReadOnly 声明一个接口

以上是关于PHP 的特征——任何现实世界的例子/最佳实践? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

突变测试在实践中有用吗?

在 LSTM 中包含分类特征和序列以进行序列预测的最佳实践?

简单接口回调例子

LIMIT MySQL php 的最佳实践

php写sql语句的最佳实践

PHP核心技术与最佳实践——全局浏览