php 特性与接口
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9205083/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Traits vs. interfaces
提问by datguywhowanders
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?
我最近一直在努力学习 PHP,但我发现自己对 trait 很着迷。我理解水平代码重用的概念,并且不想一定要从抽象类继承。我不明白的是:使用特征与接口之间的关键区别是什么?
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.
我曾尝试寻找解释何时使用其中一个的不错的博客文章或文章,但到目前为止我发现的示例看起来如此相似以至于完全相同。
采纳答案by Alec Gorge
An interface defines a set of methods that the implementing class mustimplement.
接口定义了实现类必须实现的一组方法。
When a trait is use
'd the implementations of the methods come along too--which doesn't happen in an Interface
.
当 traituse
出现时,方法的实现也会出现——这不会发生在Interface
.
That is the biggest difference.
这是最大的不同。
From the Horizontal Reuse for PHP RFC:
Traits is a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.
Traits 是一种在单继承语言(如 PHP)中代码重用的机制。Trait 旨在通过使开发人员能够在位于不同类层次结构中的多个独立类中自由重用方法集来减少单继承的一些限制。
回答by rdlowrey
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 ...
我想声明一下,我相信 trait 几乎总是一种代码味道,应该避免使用组合。我认为单继承经常被滥用到成为反模式的地步,而多继承只会加剧这个问题。在大多数情况下,通过支持组合而不是继承(无论是单个还是多个),您会得到更好的服务。如果您仍然对特征及其与接口的关系感兴趣,请继续阅读...
Let's start by saying this:
让我们先说这个:
Object-Oriented Programming (OOP) can be a difficult paradigm to grasp. Just because you're using classes doesn't mean your code is Object-Oriented (OO).
面向对象编程 (OOP) 可能是一个难以掌握的范式。仅仅因为您使用类并不意味着您的代码是面向对象 (OO)。
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 doinstead 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."
要编写 OO 代码,您需要了解 OOP 实际上与对象的功能有关。你必须根据它们能做什么而不是它们实际做什么来考虑类。这与传统的过程式编程形成鲜明对比,传统过程式编程的重点是让一些代码“做某事”。
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).
如果 OOP 代码是关于规划和设计的,那么界面就是蓝图,而对象就是完全建造的房子。同时,特质只是帮助建造由蓝图(界面)布置的房子的一种方式。
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 want to implement a caching system for a web application to cut down on server load
您想为 Web 应用程序实现缓存系统以减少服务器负载
You start out by writing a class to cache request responses using APC:
您首先编写一个类来使用 APC 缓存请求响应:
class ApcCacher
{
public function fetch($key) {
return apc_fetch($key);
}
public function store($key, $data) {
return apc_store($key, $data);
}
public function delete($key) {
return apc_delete($key);
}
}
Then, in your HTTP response object, you check for a cache hit before doing all the work to generate the actual response:
然后,在您的 HTTP 响应对象中,您在完成所有工作以生成实际响应之前检查缓存命中:
class Controller
{
protected $req;
protected $resp;
protected $cacher;
public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
$this->req = $req;
$this->resp = $resp;
$this->cacher = $cacher;
$this->buildResponse();
}
public function buildResponse() {
if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
$this->resp = $response;
} else {
// Build the response manually
}
}
public function getResponse() {
return $this->resp;
}
}
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 the ApcCacher
class. Let's say instead of the above you had made the Controller
class reliant on a CacherInterface
instead of the concrete ApcCacher
like so:
这种方法效果很好。但也许几周后,您决定要使用基于文件的缓存系统而不是 APC。现在您必须更改控制器代码,因为您已将控制器编程为使用ApcCacher
类的功能而不是表达ApcCacher
类功能的接口。让我们说代替上面的,你让Controller
类依赖于 aCacherInterface
而不是ApcCacher
像这样的具体:
// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)
To go along with that you define your interface like so:
为此,您可以像这样定义接口:
interface CacherInterface
{
public function fetch($key);
public function store($key, $data);
public function delete($key);
}
In turn you have both your ApcCacher
and your new FileCacher
classes implement the CacherInterface
and you program your Controller
class to use the capabilities required by the interface.
反过来ApcCacher
,您的FileCacher
类和新类都实现了CacherInterface
并且您对Controller
类进行了编程以使用接口所需的功能。
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.
当多个类共享相同的功能(可能由相同的接口决定)时,您应该只使用特征。使用 trait 为单个类提供功能是没有意义的:这只会混淆类的功能,更好的设计会将 trait 的功能移动到相关类中。
Consider the following trait implementation:
考虑以下特征实现:
interface Person
{
public function greet();
public function eat($food);
}
trait EatingTrait
{
public function eat($food)
{
$this->putInMouth($food);
}
private function putInMouth($food)
{
// Digest delicious food
}
}
class NicePerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Good day, good sir!';
}
}
class MeanPerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Your mother was a hamster!';
}
}
A more concrete example: imagine both your FileCacher
and your ApcCacher
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.
一个更具体的例子:想象一下你FileCacher
和你ApcCacher
在界面讨论中使用相同的方法来确定缓存条目是否过时并且应该被删除(显然现实生活中不是这种情况,但随它去)。您可以编写一个 trait 并允许两个类使用它来满足通用接口要求。
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.
最后要提醒一句:小心不要过分追求特质。当独特的类实现就足够了时,特征通常被用作糟糕设计的拐杖。您应该将特征限制为满足最佳代码设计的接口要求。
回答by Troy Alford
A trait
is essentially PHP's implementation of a mixin
, and is effectively a set of extension methods which can be added to any class through the addition of the trait
. The methods then become part of that class' implementation, but without using inheritance.
Atrait
本质上是 PHP 对 a 的实现,实际上mixin
是一组扩展方法,可以通过添加trait
. 然后这些方法成为该类实现的一部分,但不使用继承。
From the PHP Manual(emphasis mine):
从PHP 手册(强调我的):
Traits are a mechanism for code reusein single inheritance languages such as PHP. ... It is an addition to traditional inheritance and enables horizontal composition of behavior; that is, the application of class members without requiring inheritance.
Traits 是一种在单继承语言(如 PHP)中代码重用的机制。... 它是对传统继承的补充,可以实现行为的横向组合;也就是说,类成员的应用不需要继承。
An example:
一个例子:
trait myTrait {
function foo() { return "Foo!"; }
function bar() { return "Bar!"; }
}
With the above trait defined, I can now do the following:
定义了上述特征后,我现在可以执行以下操作:
class MyClass extends SomeBaseClass {
use myTrait; // Inclusion of the trait myTrait
}
At this point, when I create an instance of class MyClass
, it has two methods, called foo()
and bar()
- which come from myTrait
. And - notice that the trait
-defined methods already have a method body - which an Interface
-defined method can't.
在这一点上,当我创建一个 class 的实例时MyClass
,它有两个方法,称为foo()
and bar()
- 它们来自myTrait
. 并且 - 请注意trait
-defined 方法已经有一个方法体 - 而Interface
-defined 方法不能。
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 canhave multiple trait
inclusions - which allows the programmer to include reusable pieces - as they might if including multiple base classes.
此外 - PHP 与许多其他语言一样,使用单一继承模型- 这意味着一个类可以从多个接口派生,但不能从多个类派生。然而,一个 PHP 类可以有多个trait
包含 - 这允许程序员包含可重用的部分 - 就像它们可能包含多个基类一样。
A few things to note:
需要注意的几点:
-----------------------------------------------
| Interface | Base Class | Trait |
===============================================
> 1 per class | Yes | No | Yes |
---------------------------------------------------------------------
Define Method Body | No | Yes | Yes |
---------------------------------------------------------------------
Polymorphism | Yes | Yes | No |
---------------------------------------------------------------------
Polymorphism:
多态:
In the earlier example, where MyClass
extendsSomeBaseClass
, MyClass
isan instance of SomeBaseClass
. In other words, an array such as SomeBaseClass[] bases
can contain instances of MyClass
. Similarly, if MyClass
extended IBaseInterface
, an array of IBaseInterface[] bases
could contain instances of MyClass
. There is no such polymorphic construct available with a trait
- because a trait
is essentially just code which is copied for the programmer's convenience into each class which uses it.
在前面的示例,其中,MyClass
延伸SomeBaseClass
,MyClass
是一个实例SomeBaseClass
。换句话说,诸如此类的数组SomeBaseClass[] bases
可以包含 的实例MyClass
。类似地,如果MyClass
扩展IBaseInterface
, 的数组IBaseInterface[] bases
可能包含 的实例MyClass
。a trait
-没有这样的多态构造,因为 atrait
本质上只是为了程序员的方便而复制到使用它的每个类中的代码。
Precedence:
优先级:
As described in the Manual:
如手册中所述:
An inherited member from a base class is overridden by a member inserted by a Trait. The precedence order is that members from the current class override Trait methods, which in return override inherited methods.
从基类继承的成员被 Trait 插入的成员覆盖。优先顺序是来自当前类的成员覆盖 Trait 方法,反过来覆盖继承的方法。
So - consider the following scenario:
所以 - 考虑以下场景:
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 */ }
}
When creating an instance of MyClass, above, the following occurs:
在上面创建 MyClass 的实例时,会发生以下情况:
- The
Interface
IBase
requires a parameterless function calledSomeMethod()
to be provided. - The base class
BaseClass
provides an implementation of this method - satisfying the need. - The
trait
myTrait
provides a parameterless function calledSomeMethod()
as well, which takes precedenceover theBaseClass
-version - The
class
MyClass
provides its own version ofSomeMethod()
- which takes precedenceover thetrait
-version.
- 该
Interface
IBase
要求被称为参数的功能SomeMethod()
来提供。 - 基类
BaseClass
提供了这个方法的实现——满足需要。 - 在
trait
myTrait
提供所谓的无参数的函数SomeMethod()
,以及,其优先在BaseClass
-version - 在
class
MyClass
提供了自己的版本SomeMethod()
-其优先在trait
-version。
Conclusion
结论
- An
Interface
can not provide a default implementation of a method body, while atrait
can. - An
Interface
is a polymorphic, inheritedconstruct - while atrait
is not. - Multiple
Interface
s can be used in the same class, and so can multipletrait
s.
- 一个
Interface
不能提供一个方法体的默认实现,同时trait
可以。 - An
Interface
是一个多态的、继承的构造——而 atrait
不是。 - 多个
Interface
S能够在同一个类中使用,因此可以在多个trait
秒。
回答by J. Bruni
I think traits
are useful to create classes that contain methods which can be used as methods of several different classes.
我认为traits
创建包含可用作几个不同类的方法的方法的类很有用。
For example:
例如:
trait ToolKit
{
public $errors = array();
public function error($msg)
{
$this->errors[] = $msg;
return false;
}
}
You can have and use this "error" method in any class that usesthis trait.
您可以在任何使用此特征的类中拥有并使用此“错误”方法。
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
}
}
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, using implements
. This is not the case with traits.
虽然interfaces
你只能声明方法签名,但不能声明它的函数代码。此外,要使用界面,您需要遵循层次结构,使用implements
. 这不是特征的情况。
It is completely different!
这是完全不同的!
回答by Supun Praneeth
For beginners above answer might be difficult, This is the easiest way to understand it:
对于初学者来说,上面的答案可能很难,这是理解它的最简单方法:
Traits
性状
trait SayWorld {
public function sayHello() {
echo 'World!';
}
}
so if you want to have sayHello
function in other classes without re-creating the whole function you can use traits,
所以如果你想sayHello
在其他类中使用函数而不重新创建整个函数,你可以使用特征,
class MyClass{
use SayWorld;
}
$o = new MyClass();
$o->sayHello();
Cool right!
酷对了!
Not only functions you can use anything in the trait(function,variables,const..). also you can use multiple traits:use SayWorld,AnotherTraits;
不仅是函数,你还可以在 trait(function,variables,const..) 中使用任何东西。您也可以使用多个特征:use SayWorld,AnotherTraits;
Interface
界面
interface SayWorld {
public function sayHello();
}
class MyClass implements SayWorld {
public function sayHello() {
echo 'World!';
}
}
so this is how interface different from traits: You have to re-create everything in the interface in implemented class. interface doesn't have implementation. and interface can only have functions and const, it cannot have variables.
所以这就是接口与特征的不同之处:您必须在已实现的类中重新创建接口中的所有内容。接口没有实现。而接口只能有函数和常量,不能有变量。
I hope this helps!
我希望这有帮助!
回答by Jon Kloske
An often used metaphor to describe Traits is Traits are interfaces with implementation.
描述 Traits 的一个常用比喻是 Traits 是与实现的接口。
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) so you can't us 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.
首先,instanceof
操作符不会处理特征(即特征不是真正的对象)所以你不能用它来查看一个类是否具有某个特征(或者查看两个不相关的类是否共享一个特征) )。这就是他们所说的水平代码重用结构。
There arefunctions 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.
PHP 中现在有一些函数可以让您获得一个类使用的所有特征的列表,但是 trait-inheritance 意味着您需要进行递归检查以可靠地检查某个类是否在某个时候具有特定的特征(有示例PHP doco 页面上的代码)。但是,是的,它肯定不像 instanceof 那样简单和干净,恕我直言,它是一个可以使 PHP 更好的功能。
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:
我发现特质和接口非常适合用于创建伪多重继承。例如:
class SlidingDoor extends Door implements IKeyed
{
use KeyedTrait;
[...] // Generally not a lot else goes here since it's all in the trait
}
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.
这样做意味着您可以使用 instanceof 来确定特定的 Door 对象是否是 Keyed 的,您知道您将获得一组一致的方法等,并且所有代码都在使用 KeyedTrait 的所有类中的一个地方。
回答by Rajesh Paul
Traitsare simply for code reuse.
Traits只是为了代码重用。
Interfacejust provides the signatureof the functions that is to be defined in the classwhere it can be used depending on the programmer's discretion. Thus giving us a prototypefor a group of classes.
接口只是提供要在类中定义的函数的签名,在那里可以根据程序员的判断使用它。从而为我们提供了一组类的原型。
For reference- http://www.php.net/manual/en/language.oop5.traits.php
回答by Benj
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 更灵活,因为它们没有继承等限制。
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).
Traits 可用于将检查某些内容的方法注入到类中,例如,另一个方法或属性的存在。一篇很好的文章(但用法语,抱歉)。
For French-reading people who can get it, the GNU/Linux Magazine HS 54 has an article on this subject.
对于能读懂法语的人,GNU/Linux Magazine HS 54 有一篇关于这个主题的文章。
回答by Thielicious
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 typing use
.
如果你懂英语并且知道什么trait
意思,这正是名字所说的。它是一个无类的方法和属性包,您可以通过键入use
.
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.
基本上,您可以将其与单个变量进行比较。闭包函数可以use
从作用域外部获取这些变量,这样它们就有内部的值。它们功能强大,可以用于任何事情。如果特征被使用,也会发生同样的情况。
回答by goat
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.
同样,就像其他人提到的,traits 与接口很好地配对,允许接口指定行为契约,以及实现实现的 trait。
Adding event publish / subscribe capabilities to a class can be a common scenario in some code bases. There's 3 common solutions:
向类添加事件发布/订阅功能可能是某些代码库中的常见场景。有3种常见的解决方案:
- Define a base class with event pub/sub code, and then classes which want to offer events can extend it in order to gain the capabilities.
- Define a class with event pub/sub code, and then other classes which want to offer events can use it via composition, defining their own methods to wrap the composed object, proxying the method calls to it.
- Define a trait with event pub/sub code, and then other classes which want to offer events can
use
the trait, aka import it, to gain the capabilities.
- 定义一个带有事件发布/订阅代码的基类,然后想要提供事件的类可以扩展它以获得功能。
- 定义一个带有事件发布/订阅代码的类,然后其他想要提供事件的类可以通过组合使用它,定义自己的方法来包装组合对象,代理对它的方法调用。
- 使用事件发布/订阅代码定义一个特征,然后其他想要提供事件的类可以通过
use
该特征(即导入它)来获得功能。
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.
#1 效果不佳。直到有一天你意识到你不能扩展基类,因为你已经扩展了其他东西。我不会展示这样的例子,因为很明显,使用这样的继承会受到多大的限制。
#2 & #3 both work well. I'll show an example which highlights some differences.
#2 和 #3 都运行良好。我将展示一个突出显示一些差异的示例。
First, some code that will be the same between both examples:
首先,一些代码在两个示例之间是相同的:
An interface
一个接口
interface Observable {
function addEventListener($eventName, callable $listener);
function removeEventListener($eventName, callable $listener);
function removeAllEventListeners($eventName);
}
And some code to demonstrate usage:
以及一些演示用法的代码:
$auction = new Auction();
// Add a listener, so we know when we get a bid.
$auction->addEventListener('bid', function($bidderName, $bidAmount){
echo "Got a bid of $bidAmount from $bidderName\n";
});
// Mock some bids.
foreach (['Moe', 'Curly', 'Larry'] as $name) {
$auction->addBid($name, rand());
}
Ok, now lets show how the implementation of the Auction
class will differ when using traits.
好的,现在让我们展示Auction
使用 trait 时类的实现会有什么不同。
First, here's how #2 (using composition) would look like:
首先,这是#2(使用组合)的样子:
class EventEmitter {
private $eventListenersByName = [];
function addEventListener($eventName, callable $listener) {
$this->eventListenersByName[$eventName][] = $listener;
}
function removeEventListener($eventName, callable $listener) {
$this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
return $existingListener === $listener;
});
}
function removeAllEventListeners($eventName) {
$this->eventListenersByName[$eventName] = [];
}
function triggerEvent($eventName, array $eventArgs) {
foreach ($this->eventListenersByName[$eventName] as $listener) {
call_user_func_array($listener, $eventArgs);
}
}
}
class Auction implements Observable {
private $eventEmitter;
public function __construct() {
$this->eventEmitter = new EventEmitter();
}
function addBid($bidderName, $bidAmount) {
$this->eventEmitter->triggerEvent('bid', [$bidderName, $bidAmount]);
}
function addEventListener($eventName, callable $listener) {
$this->eventEmitter->addEventListener($eventName, $listener);
}
function removeEventListener($eventName, callable $listener) {
$this->eventEmitter->removeEventListener($eventName, $listener);
}
function removeAllEventListeners($eventName) {
$this->eventEmitter->removeAllEventListeners($eventName);
}
}
Here's how #3 (traits) would look like:
以下是#3(特征)的样子:
trait EventEmitterTrait {
private $eventListenersByName = [];
function addEventListener($eventName, callable $listener) {
$this->eventListenersByName[$eventName][] = $listener;
}
function removeEventListener($eventName, callable $listener) {
$this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
return $existingListener === $listener;
});
}
function removeAllEventListeners($eventName) {
$this->eventListenersByName[$eventName] = [];
}
protected function triggerEvent($eventName, array $eventArgs) {
foreach ($this->eventListenersByName[$eventName] as $listener) {
call_user_func_array($listener, $eventArgs);
}
}
}
class Auction implements Observable {
use EventEmitterTrait;
function addBid($bidderName, $bidAmount) {
$this->triggerEvent('bid', [$bidderName, $bidAmount]);
}
}
Note that the code inside the EventEmitterTrait
is exactly the same as what's inside the EventEmitter
class except the trait declares the triggerEvent()
method as protected. So, the only difference you need to look at is the implementation of the Auction
class.
请注意,除了 trait 将方法声明为受保护之外,EventEmitterTrait
里面的代码与类内部的代码完全相同。因此,您需要查看的唯一区别是class的实现。EventEmitter
triggerEvent()
Auction
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 the Observable
interface, we need to implement it and write boring boilerplate code that just forwards the arguments onto the corresponding method in our composed the EventEmitter
object. Using the trait in this example lets us avoid that, helping us reduce boilerplate code and improve maintainability.
而且差别很大。使用组合时,我们得到了一个很好的解决方案,允许我们EventEmitter
通过任意数量的类来重用我们的类。但是,主要的缺点是我们有很多需要编写和维护的样板代码,因为对于Observable
接口中定义的每个方法,我们需要实现它并编写无聊的样板代码,只是将参数转发到相应的方法中我们组成的EventEmitter
对象。在这个例子中使用trait 可以避免这种情况,帮助我们减少样板代码并提高可维护性。
However, there may be times where you don't want your Auction
class to implement the full Observable
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.
但是,有时您可能不希望您的Auction
类实现完整的Observable
接口——也许您只想公开 1 或 2 个方法,或者甚至根本不公开,以便您可以定义自己的方法签名。在这种情况下,您可能仍然更喜欢组合方法。
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 the EventEmitterTrait
trait too, using the EventEmitter
class implementation inside the trait :)
* 你实际上可以同时做这两个 - 定义EventEmitter
类,以防你想组合使用它,并定义EventEmitterTrait
trait,使用 trait 中的EventEmitter
类实现:)