特性与界面
我最近一直在尝试学习 PHP,我发现自己对 Trait 很着迷。我理解水平代码重用的概念,并且不想必然从抽象类继承。我不明白的是:使用特征与接口之间的关键区别是什么?
我尝试过寻找一篇不错的博客文章或文章来解释何时使用其中一种,但到目前为止我发现的示例似乎非常相似,以至于完全相同。
I've been trying to study up on PHP lately, and I find myself getting hung up on traits. I understand the concept of horizontal code reuse and not wanting to necessarily inherit from an abstract class. What I don't understand is: What is the crucial difference between using traits versus interfaces?
I've tried searching for a decent blog post or article explaining when to use one or the other, but the examples I've found so far seem so similar as to be identical.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
公共服务公告:
我想郑重声明,我认为特征几乎总是一种代码味道,应该避免使用组合。我认为单继承经常被滥用到成为反模式的地步,而多重继承只会加剧这个问题。在大多数情况下,通过组合而不是继承(无论是单个还是多个),您会得到更好的服务。如果您仍然对特征及其与接口的关系感兴趣,请继续阅读......
让我们首先这样说:
要编写 OO 代码,您需要了解 OOP 实际上与对象的功能有关。您必须根据类可以做什么来考虑类,而不是它们实际做什么。这与传统的过程式编程形成鲜明对比,传统的过程式编程的重点是让一些代码“做某事”。
如果 OOP 代码是关于规划和设计的,那么界面就是蓝图,对象就是完全构造好的房子。同时,特征只是帮助建造蓝图(界面)所布置的房子的一种方式。
接口
那么,我们为什么要使用接口呢?很简单,接口使我们的代码不那么脆弱。如果您对这种说法表示怀疑,请询问那些被迫维护不是针对接口编写的遗留代码的人。
接口是程序员和他/她的代码之间的契约。界面上写着:“只要你遵守我的规则,你就可以按照你喜欢的方式实现我,我保证不会破坏你的其他代码。”
举个例子,考虑一个现实场景(没有汽车或小部件):
您首先编写一个类来使用 APC 缓存请求响应:
然后,在 HTTP 响应对象中,在执行所有工作以生成实际响应之前检查缓存命中:
这种方法效果很好。但也许几周后您决定使用基于文件的缓存系统而不是 APC。现在您必须更改控制器代码,因为您已将控制器编程为使用
ApcCacher
类的功能,而不是表达ApcCacher
功能的接口班级。假设您不是上面那样,而是使Controller
类依赖于CacherInterface
而不是具体的ApcCacher
,如下所示:您可以这样定义接口:
反过来,您的
ApcCacher
和新的FileCacher
类都实现了CacherInterface
,并且您对进行了编程控制器类使用接口所需的功能。
这个示例(希望)演示了接口编程如何允许您更改类的内部实现,而不必担心这些更改是否会破坏您的其他代码。
Traits
另一方面, Traits 只是一种重用代码的方法。接口不应被视为特征的相互排斥的替代品。事实上,创建满足接口所需功能的特征是理想的用例。
仅当多个类共享相同的功能(可能由同一接口指定)时,才应使用特征。使用特征为单个类提供功能是没有意义的:这只会混淆该类的功能,更好的设计会将特征的功能转移到相关类中。
考虑以下特征实现:
一个更具体的示例:想象一下接口讨论中的
FileCacher
和ApcCacher
使用相同的方法来确定缓存条目是否过时,并且应该被删除(显然现实生活中不是这种情况,但就这样吧)。您可以编写一个特征并允许两个类使用它来满足公共接口要求。最后提醒一句:小心不要过度使用特质。当独特的类实现就足够时,特征通常被用作糟糕设计的拐杖。您应该限制特征以满足最佳代码设计的接口要求。
Public Service Announcement:
I want to state for the record that I believe traits are almost always a code smell and should be avoided in favor of composition. It's my opinion that single inheritance is frequently abused to the point of being an anti-pattern and multiple inheritance only compounds this problem. You'll be much better served in most cases by favoring composition over inheritance (be it single or multiple). If you're still interested in traits and their relationship to interfaces, read on ...
Let's start by saying this:
To write OO code you need to understand that OOP is really about the capabilities of your objects. You've got to think about classes in terms of what they can do instead of what they actually do. This is in stark contrast to traditional procedural programming where the focus is on making a bit of code "do something."
If OOP code is about planning and design, an interface is the blueprint and an object is the fully constructed house. Meanwhile, traits are simply a way to help build the house laid out by the blueprint (the interface).
Interfaces
So, why should we use interfaces? Quite simply, interfaces make our code less brittle. If you doubt this statement, ask anyone who's been forced to maintain legacy code that wasn't written against interfaces.
The interface is a contract between the programmer and his/her code. The interface says, "As long as you play by my rules you can implement me however you like and I promise I won't break your other code."
So as an example, consider a real-world scenario (no cars or widgets):
You start out by writing a class to cache request responses using APC:
Then, in your HTTP response object, you check for a cache hit before doing all the work to generate the actual response:
This approach works great. But maybe a few weeks later you decide you want to use a file-based cache system instead of APC. Now you have to change your controller code because you've programmed your controller to work with the functionality of the
ApcCacher
class rather than to an interface that expresses the capabilities of theApcCacher
class. Let's say instead of the above you had made theController
class reliant on aCacherInterface
instead of the concreteApcCacher
like so:To go along with that you define your interface like so:
In turn you have both your
ApcCacher
and your newFileCacher
classes implement theCacherInterface
and you program yourController
class to use the capabilities required by the interface.This example (hopefully) demonstrates how programming to an interface allows you to change the internal implementation of your classes without worrying if the changes will break your other code.
Traits
Traits, on the other hand, are simply a method for re-using code. Interfaces should not be thought of as a mutually exclusive alternative to traits. In fact, creating traits that fulfill the capabilities required by an interface is the ideal use case.
You should only use traits when multiple classes share the same functionality (likely dictated by the same interface). There's no sense in using a trait to provide functionality for a single class: that only obfuscates what the class does and a better design would move the trait's functionality into the relevant class.
Consider the following trait implementation:
A more concrete example: imagine both your
FileCacher
and yourApcCacher
from the interface discussion use the same method to determine whether a cache entry is stale and should be deleted (obviously this isn't the case in real life, but go with it). You could write a trait and allow both classes to use it to for the common interface requirement.One final word of caution: be careful not to go overboard with traits. Often traits are used as a crutch for poor design when unique class implementations would suffice. You should limit traits to fulfilling interface requirements for best code design.
接口定义了实现类必须实现的一组方法。
当一个特征被
使用
时,方法的实现也会随之而来——这不会发生在接口
中。这是最大的区别。
来自 PHP RFC 的水平重用:
An interface defines a set of methods that the implementing class must implement.
When a trait is
use
'd the implementations of the methods come along too--which doesn't happen in anInterface
.That is the biggest difference.
From the Horizontal Reuse for PHP RFC:
trait
本质上是 PHP 对mixin
的实现,并且实际上是一组扩展方法,可以通过添加trait
将其添加到任何类中代码>.然后,这些方法将成为该类实现的一部分,但不使用继承。来自 PHP 手册(强调我的):
一个例子:
定义了上述特征后,我现在可以执行以下操作:
此时,当我创建类
MyClass
的实例时,它有两个方法,称为foo() 和
bar()
- 来自myTrait
。并且 - 请注意,trait
定义的方法已经具有方法主体 - 而Interface
定义的方法却没有。此外 - PHP 与许多其他语言一样,使用单继承模型 - 这意味着一个类可以从多个接口派生,但不能从多个类派生。但是,PHP 类可以包含多个
trait
包含项 - 这允许程序员包含可重用的部分 - 就像包含多个基类一样。需要注意的一些事项:
多态性:
在前面的示例中,
MyClass
扩展SomeBaseClass,
MyClass
是SomeBaseClass
的实例。换句话说,诸如SomeBaseClass[] bases
之类的数组可以包含MyClass
的实例。同样,如果MyClass
扩展了IBaseInterface
,则IBaseInterface[]基
的数组可以包含MyClass
的实例。没有这样的多态构造可用于trait
- 因为trait
本质上只是为了程序员的方便而复制到每个使用它的类中的代码。优先级:
如手册中所述:
因此 - 考虑以下场景:
当创建上面的 MyClass 实例时,会发生以下情况:
Interface
IBase
需要一个名为SomeMethod()
的无参数函数代码> 需提供。BaseClass
提供了该方法的实现——满足需要。trait
myTrait
还提供了一个名为SomeMethod()
的无参数函数,优先于BaseClass
-versionclass
MyClass
提供了自己的SomeMethod()
版本 - 优先超过trait
版本。结论
接口
不能提供方法体的默认实现,而特征
可以。接口
是一种多态、继承构造,而特征
则不然。Interface
可以在同一个类中使用,多个trait
也可以。A
trait
is essentially PHP's implementation of amixin
, and is effectively a set of extension methods which can be added to any class through the addition of thetrait
. The methods then become part of that class' implementation, but without using inheritance.From the PHP Manual (emphasis mine):
An example:
With the above trait defined, I can now do the following:
At this point, when I create an instance of class
MyClass
, it has two methods, calledfoo()
andbar()
- which come frommyTrait
. And - notice that thetrait
-defined methods already have a method body - which anInterface
-defined method can't.Additionally - PHP, like many other languages, uses a single inheritance model - meaning that a class can derive from multiple interfaces, but not multiple classes. However, a PHP class can have multiple
trait
inclusions - which allows the programmer to include reusable pieces - as they might if including multiple base classes.A few things to note:
Polymorphism:
In the earlier example, where
MyClass
extendsSomeBaseClass
,MyClass
is an instance ofSomeBaseClass
. In other words, an array such asSomeBaseClass[] bases
can contain instances ofMyClass
. Similarly, ifMyClass
extendedIBaseInterface
, an array ofIBaseInterface[] bases
could contain instances ofMyClass
. There is no such polymorphic construct available with atrait
- because atrait
is essentially just code which is copied for the programmer's convenience into each class which uses it.Precedence:
As described in the Manual:
So - consider the following scenario:
When creating an instance of MyClass, above, the following occurs:
Interface
IBase
requires a parameterless function calledSomeMethod()
to be provided.BaseClass
provides an implementation of this method - satisfying the need.trait
myTrait
provides a parameterless function calledSomeMethod()
as well, which takes precedence over theBaseClass
-versionclass
MyClass
provides its own version ofSomeMethod()
- which takes precedence over thetrait
-version.Conclusion
Interface
can not provide a default implementation of a method body, while atrait
can.Interface
is a polymorphic, inherited construct - while atrait
is not.Interface
s can be used in the same class, and so can multipletrait
s.我认为
traits
对于创建包含可用作多个不同类的方法的方法的类很有用。例如:
您可以在任何使用此特征的类中拥有并使用此“错误”方法。
而使用
接口
时,您只能声明方法签名,而不能声明其函数的代码。此外,要使用接口,您需要遵循层次结构,使用implements
。特质的情况并非如此。这是完全不同的!
I think
traits
are useful to create classes that contain methods that can be used as methods of several different classes.For example:
You can have and use this "error" method in any class that uses this trait.
While with
interfaces
you can only declare the method signature, but not its functions' code. Also, to use an interface you need to follow a hierarchy, usingimplements
. This is not the case with traits.It is completely different!
对于初学者来说,上面的答案可能很困难,这是理解它的最简单方法:
Traits
所以如果你想在其他类中拥有
sayHello
函数而不重新创建整个函数你可以使用特质,酷吧!
不仅是函数,您还可以使用特征中的任何内容(函数、变量、常量...)。另外,您可以使用多个特征:
use SayWorld, AnotherTraits;
Interface
这就是接口与特征的不同之处:您必须在已实现的接口中重新创建接口中的所有内容。班级。接口没有实现,接口只能有函数和常量,不能有变量。
我希望这有帮助!
For beginners above answer might be difficult, this is the easiest way to understand it:
Traits
so if you want to have
sayHello
function in other classes without re-creating the whole function you can use traits,Cool right!
Not only functions you can use anything in the trait(function, variables, const...). Also, you can use multiple traits:
use SayWorld, AnotherTraits;
Interface
So this is how interfaces differ from traits: You have to re-create everything in the interface in an implemented class. Interfaces don't have an implementation and interfaces can only have functions and constants, it cannot have variables.
I hope this helps!
特征只是为了代码重用。
接口仅提供要在类中定义的函数的签名,可以根据程序员的判断来使用它。从而为我们提供了一组类的原型。
供参考-
http://www.php.net/manual/en/language.oop5 .traits.php
Traits are simply for code reuse.
Interface just provides the signature of the functions that is to be defined in the class where it can be used depending on the programmer's discretion. Thus giving us a prototype for a group of classes.
For reference-
http://www.php.net/manual/en/language.oop5.traits.php
在大多数情况下,这是一种很好的思考方式,但两者之间存在许多细微的差异。
首先,
instanceof
运算符不适用于特征(即特征不是真实的对象),因此您不能使用它来查看类是否具有特定特征(或看看两个不相关的类是否共享一个特征)。这就是他们所说的水平代码重用结构的含义。PHP 中现在有一些函数可以让您获取类使用的所有特征的列表,但是特征继承意味着您需要进行递归检查以可靠地检查某个类是否在某个时刻有一个特定的特征(PHP doco 页面上有示例代码)。但是,是的,它肯定不像
instanceof
那样简单和干净,而且恕我直言,它是一个可以让 PHP 变得更好的功能。此外,抽象类仍然是类,因此它们不能解决与多重继承相关的代码重用问题。请记住,您只能扩展一个类(真实的或抽象的),但可以实现多个接口。
我发现特征和接口非常适合一起使用来创建伪多重继承。例如:
这样做意味着您可以使用
instanceof
来确定特定的 Door 对象是否是 Keyed 的,您知道您将获得一组一致的方法等,并且所有代码都在一个地方跨所有使用 KeyedTrait 的类。This is a good way of thinking about it in most circumstances, but there are a number of subtle differences between the two.
For a start, the
instanceof
operator will not work with traits (ie, a trait is not a real object), therefore you can't use that to see if a class has a certain trait (or to see if two otherwise unrelated classes share a trait). That's what they mean by it being a construct for horizontal code re-use.There are functions now in PHP that will let you get a list of all the traits a class uses, but trait-inheritance means you'll need to do recursive checks to reliably check if a class at some point has a specific trait (there's example code on the PHP doco pages). But yeah, it's certainly not as simple and clean as
instanceof
is, and IMHO it's a feature that would make PHP better.Also, abstract classes are still classes, so they don't solve multiple-inheritance related code re-use problems. Remember you can only extend one class (real or abstract) but implement multiple interfaces.
I've found traits and interfaces are really good to use hand in hand to create pseudo multiple inheritance. Eg:
Doing this means you can use
instanceof
to determine if the particular Door object is Keyed or not, you know you'll get a consistent set of methods, etc, and all the code is in one place across all the classes that use the KeyedTrait.基本上,您可以将特征视为代码的自动“复制粘贴”。
使用特征是危险的,因为在执行之前无法知道它的作用。
然而,特征由于缺乏继承等限制而更加灵活。
特征对于注入一个方法非常有用,该方法可以检查类中的某些内容,例如另一个方法或属性是否存在。 一篇关于此的好文章(但是是法语,抱歉) 。
对于读法语的人来说,GNU/Linux Magazine HS 54 有一篇关于这个主题的文章。
You can consider a trait as an automated "copy-paste" of code, basically.
Using traits is dangerous since there is no mean to know what it does before execution.
However, traits are more flexible because of their lack of limitations such as inheritance.
Traits can be useful to inject a method which checks something into a class, for example, the existence of another method or attribute. A nice article on that (but in French, sorry).
For French-reading people who can get it, the GNU/Linux Magazine HS 54 has an article on this subject.
如果您懂英语并且知道
trait
的含义,那么它就如其名称所示。它是一个无类的方法和属性包,您可以通过键入use
附加到现有类。基本上,您可以将其与单个变量进行比较。闭包函数可以从作用域之外
使用
这些变量,这样它们就可以在作用域内拥有值。它们功能强大,可以用于任何事情。如果使用特征,也会发生同样的情况。If you know English and know what
trait
means, it is exactly what the name says. It is a class-less pack of methods and properties you attach to existing classes by typinguse
.Basically, you could compare it to a single variable. Closures functions can
use
these variables from outside of the scope and that way they have the value inside. They are powerful and can be used in everything. Same happens to traits if they are being used.其他答案很好地解释了接口和特征之间的差异。我将重点关注一个有用的现实世界示例,特别是演示特征可以使用实例变量的示例 - 允许您使用最少的样板代码向类添加行为。
同样,就像其他人提到的那样,特征与接口很好地配对,允许接口指定行为契约,并让特征来实现实现。
在某些代码库中,向类添加事件发布/订阅功能可能是常见场景。有 3 种常见的解决方案:
每项效果如何?
#1 效果不佳。会的,直到有一天您意识到您无法扩展基类,因为您已经扩展了其他内容。我不会展示这样的示例,因为使用这样的继承的限制应该是显而易见的。
#2 & #3 两者都运作良好。我将展示一个突出显示一些差异的示例。
首先,两个示例之间的一些代码是相同的:
一个接口
和一些演示用法的代码:
好的,现在让我们展示使用特征时
Auction
类的实现有何不同。首先,#2(使用组合)如下所示:
#3(特征)如下所示:
请注意,
EventEmitterTrait
内的代码与内的代码完全相同EventEmitter
类,但特征将triggerEvent()
方法声明为受保护。因此,您需要注意的唯一区别是Auction
类的实现。而且差别很大。当使用组合时,我们得到了一个很好的解决方案,允许我们通过任意数量的类重用我们的
EventEmitter
。但是,主要缺点是我们需要编写和维护大量样板代码,因为对于 Observable 接口中定义的每个方法,我们需要实现它并编写无聊的样板代码将参数转发到我们组合的 EventEmitter 对象中的相应方法。使用此示例中的特征可以让我们避免这种情况,帮助我们减少样板代码并提高可维护性。但是,有时您可能不希望您的
Auction
类实现完整的Observable
接口 - 也许您只想公开 1 或 2 个方法,甚至可能是根本不需要,这样您就可以定义自己的方法签名。在这种情况下,您可能仍然更喜欢组合方法。但是,这个特性在大多数情况下都非常引人注目,特别是当接口有很多方法时,这会导致您编写大量样板文件。
* 实际上,您可以同时执行这两种操作 - 定义
EventEmitter
类,以防您想要组合使用它,并使用EventEmitter< 定义
EventEmitterTrait
特征/code> 特征内的类实现:)Other answers did a great job of explaining differences between interfaces and traits. I will focus on a useful real world example, in particular one which demonstrates that traits can use instance variables - allowing you add behavior to a class with minimal boilerplate code.
Again, like mentioned by others, traits pair well with interfaces, allowing the interface to specify the behavior contract, and the trait to fulfill the implementation.
Adding event publish / subscribe capabilities to a class can be a common scenario in some code bases. There's 3 common solutions:
use
the trait, aka import it, to gain the capabilities.How well does each work?
#1 Doesn't work well. It would, until the day you realize you can't extend the base class because you're already extending something else. I won't show an example of this because it should be obvious how limiting it is to use inheritance like this.
#2 & #3 both work well. I'll show an example which highlights some differences.
First, some code that will be the same between both examples:
An interface
And some code to demonstrate usage:
Ok, now lets show how the implementation of the
Auction
class will differ when using traits.First, here's how #2 (using composition) would look like:
Here's how #3 (traits) would look like:
Note that the code inside the
EventEmitterTrait
is exactly the same as what's inside theEventEmitter
class except the trait declares thetriggerEvent()
method as protected. So, the only difference you need to look at is the implementation of theAuction
class.And the difference is large. When using composition, we get a great solution, allowing us to reuse our
EventEmitter
by as many classes as we like. But, the main drawback is the we have a lot of boilerplate code that we need to write and maintain because for each method defined in theObservable
interface, we need to implement it and write boring boilerplate code that just forwards the arguments onto the corresponding method in our composed theEventEmitter
object. Using the trait in this example lets us avoid that, helping us reduce boilerplate code and improve maintainability.However, there may be times where you don't want your
Auction
class to implement the fullObservable
interface - maybe you only want to expose 1 or 2 methods, or maybe even none at all so that you can define your own method signatures. In such a case, you might still prefer the composition method.But, the trait is very compelling in most scenarios, especially if the interface has lots of methods, which causes you to write lots of boilerplate.
* You could actually kinda do both - define the
EventEmitter
class in case you ever want to use it compositionally, and define theEventEmitterTrait
trait too, using theEventEmitter
class implementation inside the trait :)主要区别在于,对于接口,您必须在实现所述接口的每个类中定义每个方法的实际实现,因此您可以让许多类实现相同的接口但具有不同的行为,而特征只是注入的代码块一个班级;另一个重要的区别是特征方法只能是类方法或静态方法,这与接口方法不同,接口方法也可以(通常是)实例方法。
The main difference is that, with interfaces, you must define the actual implementation of each method within each class that implements said interface, so you can have many classes implement the same interface but with different behavior, while traits are just chunks of code injected in a class; another important difference is that trait methods can only be class-methods or static-methods, unlike interface methods which can also (and usually are) be instance methods.
该特征与我们可以用于多重继承目的和代码可重用性的类相同。
我们可以在类中使用特征,也可以通过“use keywords”在同一个类中使用多个特征。
接口用于代码可重用性,与特征相同
,接口扩展了多个接口,因此我们可以解决多重继承问题,但是当我们实现接口时,我们应该在类中创建所有方法。
欲了解更多信息,请点击下面的链接:
http://php.net/manual/en /语言.oop5.traits.php
http://php.net/manual/en/language.oop5.interfaces。 php
The trait is same as a class we can use for multiple inheritance purposes and also code reusability.
We can use trait inside the class and also we can use multiple traits in the same class with 'use keyword'.
The interface is using for code reusability same as a trait
the interface is extend multiple interfaces so we can solve the multiple inheritance problems but when we implement the interface then we should create all the methods inside the class.
For more info click below link:
http://php.net/manual/en/language.oop5.traits.php
http://php.net/manual/en/language.oop5.interfaces.php
接口是一个契约,它规定“这个对象能够做这件事”,而特征则赋予对象做这件事的能力。
特征本质上是一种在类之间“复制和粘贴”代码的方法。
尝试阅读这篇文章,什么是 PHP 特征?
An interface is a contract that says “this object is able to do this thing”, whereas a trait is giving the object the ability to do the thing.
A trait is essentially a way to “copy and paste” code between classes.
Try reading this article, What are PHP traits?