Traits 是PHP 5.4中最大的新增功能之一,它可以实现代码的横向重用,以支持日志记录、安全性、缓存等常见需求。
然而,我仍不知道如何在我的项目中使用traits。
是否有任何已经使用traits的开源项目?是否有任何关于如何使用traits构建架构的好文章/阅读材料?
Traits 是PHP 5.4中最大的新增功能之一,它可以实现代码的横向重用,以支持日志记录、安全性、缓存等常见需求。
然而,我仍不知道如何在我的项目中使用traits。
是否有任何已经使用traits的开源项目?是否有任何关于如何使用traits构建架构的好文章/阅读材料?
我猜想现在需要花费一些时间学习具有特质的语言,以了解被接受的最佳实践。我目前对特质的看法是,你应该只在其他共享相同功能的类中需要重复代码时使用它们。
例如,一个记录器特质:
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);
使用traits时需要考虑的重要事项是,它们实际上只是被复制到类中的代码片段。这很容易导致冲突,例如当您尝试更改方法的可见性时。
trait T {
protected function foo() {}
}
class A {
public function foo() {}
}
class B extends A
{
use T;
}
trait T {
public function foo() {
return 1;
}
}
class A {
use T;
public function foo() {
return 2;
}
}
$a = new A;
echo $a->foo();
将会打印出2 (演示)。这些是你需要避免的事情,因为它们让错误难以发现。你还应该避免将操作类属性或方法的trait放入使用它的类中。
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();
这段代码可以正常工作(演示),但现在这个Trait与A紧密耦合,水平重用的整个概念都丢失了。
当你遵循接口隔离原则时,你会有许多小的类和接口。这使得Trait成为你所提到的事情的理想选择,例如横切关注点,但不适合组合对象(从结构上来说)。在上面的Logger示例中,Trait是完全隔离的。它没有依赖于具体类。
我们可以使用聚合/组合(如本页其他地方所示)来实现相同的结果类,但使用聚合/组合的缺点是我们将不得不手动添加代理/委托方法到每个应该能够记录日志的类中。Traits通过允许我将样板放在一个地方并有选择地应用它解决了这个问题。
注意:鉴于Traits是PHP中的一个新概念,上述所有意见都可能会改变。我还没有太多时间来评估这个概念。但我希望它足以给你一些思考。
我个人的观点是,在编写清晰代码时,traits 的应用实际上非常有限。
与其使用 traits 来将代码“hack”到类中,不如通过构造函数或 setter 方法传递依赖项更好:
class ClassName {
protected $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
// or
public function setLogger(LoggerInterface $logger) {
$this->logger = $logger;
}
}
我认为这样做比使用特征更好的主要原因在于,通过删除对特征的硬编码,您的代码变得更加灵活。例如,现在您可以简单地传递不同的记录器类。这使得您的代码可重用和可测试。
:) 我不喜欢推测和辩论关于某事应该怎样做。在这种情况下是特质。我将展示给你特质在哪些方面有用,你可以从中学习,或者忽略它。
特质(Traits) - 它们非常适用于应用策略(strategies)。简而言之,策略设计模式在需要以不同方式处理(过滤、排序等)相同数据时非常有用。
例如,你有一个产品列表,想要根据某些标准(品牌、规格等)进行筛选,或按不同方式(价格、标签等)排序。你可以创建一个排序特质,其中包含不同的函数用于不同类型的排序(数字、字符串、日期等)。然后你不仅可以在产品类中使用这个特质(如示例所示),还可以在其他需要类似策略(对一些数据应用数字排序等)的类中使用。
试一试:
<?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>';
?>
作为结束语,我想到类似于配件的特性(它们可以用来改变我的数据)。类似的方法和属性可以从我的类中剪切出来,放入一个单独的地方进行简单的维护,使代码更加简洁。
trait ReadOnly{
protected $readonly = false;
public function setReadonly($value){ $this->readonly = (bool)$value; }
public function getReadonly($value){ return $this->readonly; }
}
if($this -> getReadonly($value))
;但是如果您没有使用这个 trait,这将会生成一个错误。因此,这个例子是有缺陷的。 - Luceos