Answers:
我个人的观点是,在编写简洁的代码时,实际上很少使用特质。
与其使用特征将代码入侵到类中,不如通过构造函数或setter传递依赖项:
class ClassName {
protected $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
// or
public function setLogger(LoggerInterface $logger) {
$this->logger = $logger;
}
}
我发现比使用特征更好的主要原因是,通过消除与特征的硬耦合,您的代码更加灵活。例如,您现在可以简单地传递另一个记录器类。这使您的代码可重用和可测试。
我想人们现在必须研究具有特质的语言一段时间,以学习公认的良好/最佳实践。我目前对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;
}
然后你做(演示)
$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(演示)。您将要避免这些事情,因为它们会使错误很难发现。您还希望避免将事物放入对使用它的类的属性或方法进行操作的特征中,例如
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成为您提到的事物(例如,横切关注点)而不是构成对象(在结构意义上)的理想人选。在上面的Logger示例中,特征是完全隔离的。它不依赖于具体类。
我们可以使用 聚合/组合(如本页其他位置所示)来获得相同的结果类,但是使用聚合/组合的缺点是我们必须手动将proxy / delegator方法添加到每个类中,能够登录。特质很好地解决了这一问题,方法是允许我将样板放置在一个位置,然后在需要的地方选择性地应用它。
注意:鉴于trait是PHP中的一个新概念,因此上面表达的所有观点可能会发生变化。我还没有太多时间自己评估这个概念。但我希望能给您一些思考的足够好。
:)我不喜欢理论化和辩论某些事情应该做什么。在这种情况下,特质。我将向您展示我发现对自己有用的特质,您可以从中学习或忽略。
特性 -他们非常擅长运用策略。简而言之,当您希望以不同方式处理(过滤,排序等)相同数据时,策略设计模式很有用。
例如,您有一个要根据某些条件(品牌,规格等)或以不同方式(价格,标签等)进行筛选的产品列表。您可以创建一个排序特征,其中包含针对不同排序类型(数字,字符串,日期等)的不同功能。然后,不仅可以在产品类(如示例中给出)中使用此特征,还可以在需要类似策略(将数字排序应用于某些数据等)的其他类中使用此特征。
试试吧:
<?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
。
我对Traits感到很兴奋,因为它们在为Magento电子商务平台开发扩展时解决了一个常见问题。当扩展通过扩展将功能添加到核心类(例如User模型)时,就会出现问题。通过指向Zend自动加载器(通过XML配置文件)使用扩展中的User模型,并让该新模型扩展核心模型,即可完成此操作。(示例)但是如果两个扩展覆盖同一模型怎么办?您得到一个“竞赛条件”,并且只有一个被加载。
现在的解决方案是编辑扩展,以便一个扩展一个链中另一个模型的覆盖类,然后设置扩展配置以正确的顺序加载它们,以便继承链起作用。
该系统经常会导致错误,并且在安装新的扩展程序时,有必要检查冲突并编辑扩展程序。这很痛苦,并且破坏了升级过程。
我认为,如果没有此令人讨厌的模型覆盖“竞赛条件”,使用特质将是完成同一件事的好方法。当然,如果多个Traits实现具有相同名称的方法仍然会发生冲突,但是我想像一个简单的命名空间约定就可以在很大程度上解决此问题。
TL; DR我认为Traits对于为大型PHP软件包(如Magento)创建扩展/模块/插件很有用。
您可能对只读对象具有如下特征:
trait ReadOnly{
protected $readonly = false;
public function setReadonly($value){ $this->readonly = (bool)$value; }
public function getReadonly($value){ return $this->readonly; }
}
您可以检测是否使用了该特征并确定是否应该将该对象写入数据库,文件等中。
use
这种特点的班级就会召集if($this -> getReadonly($value))
; 但是,如果您没有use
此特征,则会产生错误。因此,这个例子是有缺陷的。