最近我一直在努力学习PHP,我发现自己被trait缠住了。我理解横向代码重用的概念,并且不希望必然地继承抽象类。我不明白的是:使用特征和使用界面之间的关键区别是什么?
我曾试着搜索过一篇像样的博客文章或文章,解释什么时候使用其中一种或另一种,但到目前为止,我找到的例子似乎非常相似,甚至完全相同。
最近我一直在努力学习PHP,我发现自己被trait缠住了。我理解横向代码重用的概念,并且不希望必然地继承抽象类。我不明白的是:使用特征和使用界面之间的关键区别是什么?
我曾试着搜索过一篇像样的博客文章或文章,解释什么时候使用其中一种或另一种,但到目前为止,我找到的例子似乎非常相似,甚至完全相同。
当前回答
主要的区别在于,对于接口,您必须在实现上述接口的每个类中定义每个方法的实际实现,因此您可以让许多类实现相同的接口但具有不同的行为,而trait只是注入到类中的代码块;另一个重要的区别是trait方法只能是类方法或静态方法,不像接口方法也可以(通常是)是实例方法。
其他回答
对于初学者来说,上面的答案可能很难,下面是最简单的理解方法:
特征
trait SayWorld {
public function sayHello() {
echo 'World!';
}
}
所以如果你想在其他类中使用sayHello函数,而不需要重新创建整个函数,你可以使用trait,
class MyClass{
use SayWorld;
}
$o = new MyClass();
$o->sayHello();
酷吧!
不只是函数,你可以使用trait中的任何东西(function, variables, const…)你也可以使用多个trait: SayWorld, AnotherTraits;
接口
interface SayWorld {
public function sayHello();
}
class MyClass implements SayWorld {
public function sayHello() {
echo 'World!';
}
}
因此,这就是接口与特征的不同之处:您必须在实现的类中重新创建接口中的所有内容。接口没有实现,接口只能有函数和常量,不能有变量。
我希望这能有所帮助!
trait本质上是PHP对mixin的实现,实际上是一组扩展方法,可以通过添加trait将其添加到任何类中。然后,这些方法成为该类实现的一部分,但不使用继承。
来自PHP手册(强调我的):
trait是单继承语言(如PHP. ...)中代码重用的一种机制它是对传统继承的补充,并支持行为的水平组合;也就是说,类成员的应用不需要继承。
一个例子:
trait myTrait {
function foo() { return "Foo!"; }
function bar() { return "Bar!"; }
}
有了上面的特征,我现在可以做以下事情:
class MyClass extends SomeBaseClass {
use myTrait; // Inclusion of the trait myTrait
}
在这一点上,当我创建一个MyClass类的实例时,它有两个方法,称为foo()和bar()——它们来自myTrait。注意,trait定义的方法已经有一个方法体,而接口定义的方法没有。
此外,PHP与许多其他语言一样,使用单一继承模型——这意味着一个类可以从多个接口派生,但不能从多个类派生。但是,PHP类可以包含多个trait——这允许程序员包含可重用的部分——就像包含多个基类一样。
有几件事需要注意:
-----------------------------------------------
| Interface | Base Class | Trait |
===============================================
> 1 per class | Yes | No | Yes |
---------------------------------------------------------------------
Define Method Body | No | Yes | Yes |
---------------------------------------------------------------------
Polymorphism | Yes | Yes | No |
---------------------------------------------------------------------
多态:
在前面的例子中,MyClass扩展了SomeBaseClass, MyClass是SomeBaseClass的一个实例。换句话说,像SomeBaseClass[] bases这样的数组可以包含MyClass的实例。类似地,如果MyClass扩展了IBaseInterface,则IBaseInterface[]基数组可以包含MyClass的实例。trait不存在这样的多态结构——因为trait本质上只是为了程序员方便而复制到每个使用它的类中的代码。
优先级:
如手册所述:
从基类继承的成员被Trait插入的成员覆盖。优先级顺序是当前类的成员重写Trait方法,Trait方法反过来重写继承的方法。
所以,考虑下面的场景:
class BaseClass {
function SomeMethod() { /* Do stuff here */ }
}
interface IBase {
function SomeMethod();
}
trait myTrait {
function SomeMethod() { /* Do different stuff here */ }
}
class MyClass extends BaseClass implements IBase {
use myTrait;
function SomeMethod() { /* Do a third thing */ }
}
当创建MyClass的实例时,会发生以下情况:
Interface IBase需要提供一个名为SomeMethod()的无参数函数。 基类BaseClass提供了该方法的实现—满足需求。 trait myTrait也提供了一个名为SomeMethod()的无参数函数,它优先于baseclass版本 类MyClass提供了它自己的SomeMethod()版本-它优先于特征版本。
结论
Interface不能提供方法体的默认实现,而trait可以。 接口是一种多态的、可继承的结构,而特征则不是。 在同一个职业中可以使用多个接口,也可以使用多个特征。
主要的区别在于,对于接口,您必须在实现上述接口的每个类中定义每个方法的实际实现,因此您可以让许多类实现相同的接口但具有不同的行为,而trait只是注入到类中的代码块;另一个重要的区别是trait方法只能是类方法或静态方法,不像接口方法也可以(通常是)是实例方法。
我认为trait对于创建包含方法的类是有用的,这些方法可以被用作几个不同类的方法。
例如:
trait ToolKit
{
public $errors = array();
public function error($msg)
{
$this->errors[] = $msg;
return false;
}
}
您可以在任何使用此特性的类中拥有并使用此“error”方法。
class Something
{
use Toolkit;
public function do_something($zipcode)
{
if (preg_match('/^[0-9]{5}$/', $zipcode) !== 1)
return $this->error('Invalid zipcode.');
// do something here
}
}
而对于接口,你只能声明方法签名,而不能声明它的函数代码。此外,要使用接口,您需要遵循层次结构,使用实现。但特质却不是这样。
这是完全不同的!
trait只是为了代码重用。
Interface只是提供了要在类中定义的函数的签名,它可以根据程序员的判断使用。这样就为我们提供了一组类的原型。
供参考, http://www.php.net/manual/en/language.oop5.traits.php