特性是PHP 5.4的最大增加之一.我知道语法并理解特征背后的想法,比如日志,安全性,缓存等常见内容的水平代码重用.
但是,我仍然不知道如何在我的项目中使用特征.
是否有任何已经使用特征的开源项目?关于如何使用特征构建体系结构的任何好文章/阅读材料?
Gor*_*don 195
我想有一段时间我必须研究具有Traits的语言才能学习公认的Good/Best实践.我目前对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;
}
Run Code Online (Sandbox Code Playgroud)
然后你做(演示)
$foo = new Foo;
$foo->setLogger(new DemoLogger);
$foo->log('It works', 1);
Run Code Online (Sandbox Code Playgroud)
我想在使用traits时要考虑的重要事项是它们实际上只是被复制到类中的代码片段.例如,当您尝试更改方法的可见性时,这很容易导致冲突,例如
trait T {
protected function foo() {}
}
class A {
public function foo() {}
}
class B extends A
{
use T;
}
Run Code Online (Sandbox Code Playgroud)
以上将导致错误(演示).同样,在trait中声明的任何已经在using类中声明的方法也不会被复制到类中,例如
trait T {
public function foo() {
return 1;
}
}
class A {
use T;
public function foo() {
return 2;
}
}
$a = new A;
echo $a->foo();
Run Code Online (Sandbox Code Playgroud)
将打印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();
Run Code Online (Sandbox Code Playgroud)
工作(演示),但现在这个特性与A密切相关,水平重用的整个想法都失去了.
当您遵循接口隔离原则时,您将拥有许多小类和接口.这使得Traits成为您提到的事物的理想候选者,例如横切关注点,而不是组合对象(在结构意义上).在上面的Logger示例中,特征是完全隔离的.它对具体类没有依赖性.
我们可以使用聚合/组合(如本页其他地方所示)来实现相同的结果类,但使用聚合/组合的缺点是我们必须手动将代理/委托方法添加到每个类然后应该能够记录.Traits通过允许我将样板保存在一个位置并在需要时选择性地应用它来很好地解决这个问题.
注意:鉴于特征是PHP中的一个新概念,上面表达的所有观点都可能发生变化.我自己还没有太多时间来评估这个概念.但我希望能给你一些思考的东西已经足够了.
Nik*_*kiC 86
我个人的观点是,在编写干净的代码时,实际上很少有特征应用.
不使用traits将代码破解到类中,最好通过构造函数或通过setter传递依赖项:
class ClassName {
protected $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
// or
public function setLogger(LoggerInterface $logger) {
$this->logger = $logger;
}
}
Run Code Online (Sandbox Code Playgroud)
我发现比使用特征更好的主要原因是通过删除与特征的硬耦合,您的代码更灵活.例如,您现在可以简单地传递一个不同的记录器类.这使您的代码可重用且可测试.
小智 19
:)我不喜欢理论和争论应该做些什么.在这种情况下的特征.我将向您展示我发现的有用的特征,您可以从中学习,也可以忽略它.
特质 - 他们很适合应用策略.简而言之,当您希望以不同方式处理(过滤,排序等)相同数据时,策略设计模式非常有用.
例如,您有一个要根据某些标准(品牌,规格,等等)过滤掉的产品列表,或者按不同方式(价格,标签等)进行排序.您可以创建包含不同排序类型(数字,字符串,日期等)的不同函数的排序特征.然后,您不仅可以在产品类中使用此特征(如示例中所示),还可以在需要类似策略的其他类中使用此特征(对某些数据应用数字排序等).
试试吧:
<?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>';
?>
Run Code Online (Sandbox Code Playgroud)
作为结束语,我考虑了配件之类的特性(我可以用它来改变我的数据).类似的方法和属性可以从我的类中删除并放在一个地方,以便于维护,更短和更清晰的代码.
我对 Traits 感到兴奋,因为它们解决了为 Magento 电子商务平台开发扩展时的一个常见问题。当扩展通过扩展向核心类(比如用户模型)添加功能时,就会出现问题。这是通过指向 Zend 自动加载器(通过 XML 配置文件)使用扩展中的用户模型来完成的,并让新模型扩展核心模型。(示例)但是如果两个扩展覆盖了同一个模型呢?你得到一个“竞争条件”,并且只加载了一个。
现在的解决方案是编辑扩展,这样一个扩展在链中扩展另一个的模型覆盖类,然后设置扩展配置以正确的顺序加载它们,以便继承链工作。
该系统经常导致错误,并且在安装新扩展时需要检查冲突并编辑扩展。这很痛苦,并且会中断升级过程。
我认为使用 Traits 将是完成相同事情的好方法,而无需这种烦人的模型覆盖“竞争条件”。当然,如果多个 Traits 实现具有相同名称的方法,仍然可能存在冲突,但我想像一个简单的命名空间约定可以在很大程度上解决这个问题。
TL;DR 我认为 Traits 可用于为 Magento 等大型 PHP 软件包创建扩展/模块/插件。