PHP trait 允许我们存储一组属性方法,以便在可能需要它们的类中轻松重用。
“特质”一词可以从字面上理解,因为它用于定义在多个类之间共享的“特质”。
这并不意味着一切都非常适合一个特征。
几个月来我一直在考虑这个主题,但我仍然没有完全决定如何最好地使用它们。
但是,似乎至少,一种合适的用途是定义魔术方法。
例如,我们可能正在为多个类定义诸如 __get 或者 __set 之类的魔术方法;我们可以创建一个特征并将其加载到每个类中,而不是在所有需要它的类中重复相同的代码。
下面是我创建的一个简单示例特征,用于防止向类实例添加属性:
namespace doorkeeper\lib\class_traits; trait no_set { public function __set($name, $value) { throw new \Exception("Adding new properties is not allowed on " . __CLASS__); } }
要在其他类中使用 trait:
class some_class { public $message = 'hallo'; public function show_message() { echo $this->$message;exit(); } use \doorkeeper\lib\class_traits\no_set; }
尝试通过 $some_class->some_new_property = 'value'; 创建新属性,在实例化类后,现在会触发异常;但我们仍然可以更改现有属性的值。
即:$some_class->message = 'goodbye';
继承与特征
我个人倾向于使用依赖注入在类之间共享代码,避免使用继承和特征;事实上,扩展类可能最好保留用于处理其他人的代码,而 trait 似乎很少有好的用例。
如果我们不拥有代码,我们可以考虑使用扩展,至少有以下几个原因:
- 扩展一个类比修改整个实现更容易。
- 它使我们自己的代码与我们正在扩展的类中的代码清楚地分开。
- 如果类被更新,你的代码很少会停止工作,如果是这样,修复它会更容易。
- 当我们可以直接处理代码时,扩展我们自己的代码几乎没有意义。
你不应该使用特性来改变其他人的代码,因为它需要对原始类进行修改——使用扩展来“扩展”他们的代码。
不要将特征用作经常重复使用的复制粘贴代码的通用方式。
考虑代码是否更适合我们可以重用和独立实例化的专用类 - 然后根据需要通过 DI 提供它。
依赖注入与特征
我真的不认为有竞争。
每当我考虑为某事使用特征时,我总是最终将其重构为 DI。
我的主要原因是能够独立实例化代码通常有很大的好处。
所有这些 use 声明也是一个等待发生的重构问题。
如果一个特性被移动,你将需要更新所有使用它的类。
使用 DI,我们只需从一个中心位置传递对象,例如组合根——移动原始类文件不会在其他地方破坏应用程序。
很多人不喜欢 DI,因为他们需要传递很长的依赖项列表;作为替代方案,我们可以创建一个容器对象来保留常用对象。
如果项目变得足够大,那么我们可能会发现某些对象实际上在整个项目中“全局”使用;将它们保存在容器中是一种不错的方法,当其他地方需要代码时,它仍然允许独立实例化。
另一种选择是创建工厂类来创建对象。
在这些内部,我们可以直接使用 new 关键字创建依赖项。
如上所述,通常不建议在依赖它们的类中创建依赖项,因为如果发生某些变化,我们可能需要更新所有使用这些依赖项的类 - 在工厂类中完成时,情况就不同了,然而。
“工厂”只是一个用来描述普通 PHP 类功能的术语。
例如,PHP 中没有“工厂”关键字。
工厂只是用于创建其他对象的对象。
Traits 可能还有一些用处,只是我还没有真正找到它们。
目前只有一种情况我正在使用特征,那就是改变由 PHP 实现的某些魔术方法的行为。
我认为防止用户修改我们的实例化对象是一种很好的做法,为此,我创建了一个我不想更改的类中使用的特性。
trait no_set { public function __set($name, $value) { throw new Exception("Adding new properties is not allowed on " . __CLASS__); } }
通过这种方式,特征可以成为避免编码时重复的非常有用的方法。
但是,在大多数情况下,从长远来看,我们可能会从使用 DI(依赖注入)中获益更多。