特性与界面

发布于 2025-01-04 05:10:13 字数 156 浏览 1 评论 0原文

我最近一直在尝试学习 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

櫻之舞 2025-01-11 05:10:13

公共服务公告:

我想郑重声明,我认为特征几乎总是一种代码味道,应该避免使用组合。我认为单继承经常被滥用到成为反模式的地步,而多重继承只会加剧这个问题。在大多数情况下,通过组合而不是继承(无论是单个还是多个),您会得到更好的服务。如果您仍然对特征及其与接口的关系感兴趣,请继续阅读......


让我们首先这样说:

面向对象编程(OOP)可能是一个难以掌握的范例。
仅仅因为您使用类并不意味着您的代码是
面向对象(OO)。

要编写 OO 代码,您需要了解 OOP 实际上与对象的功能有关。您必须根据类可以做什么来考虑类,而不是它们实际做什么。这与传统的过程式编程形成鲜明对比,传统的过程式编程的重点是让一些代码“做某事”。

如果 OOP 代码是关于规划和设计的,那么界面就是蓝图,对象就是完全构造好的房子。同时,特征只是帮助建造蓝图(界面)所布置的房子的一种方式。

接口

那么,我们为什么要使用接口呢?很简单,接口使我们的代码不那么脆弱。如果您对这种说法表示怀疑,请询问那些被迫维护不是针对接口编写的遗留代码的人。

接口是程序员和他/她的代码之间的契约。界面上写着:“只要你遵守我的规则,你就可以按照你喜欢的方式实现我,我保证不会破坏你的其他代码。”

举个例子,考虑一个现实场景(没有汽车或小部件):

您想要为 Web 应用程序实现一个缓存系统来剪切
降低服务器负载

您首先编写一个类来使用 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);
  }
}

然后,在 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;
  }
}

这种方法效果很好。但也许几周后您决定使用基于文件的缓存系统而不是 APC。现在您必须更改控制器代码,因为您已将控制器编程为使用 ApcCacher 类的功能,而不是表达 ApcCacher 功能的接口班级。假设您不是上面那样,而是使 Controller 类依赖于 CacherInterface 而不是具体的 ApcCacher ,如下所示

// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)

:您可以这样定义接口:

interface CacherInterface
{
  public function fetch($key);
  public function store($key, $data);
  public function delete($key);
}

反过来,您的 ApcCacher 和新的 FileCacher 类都实现了 CacherInterface,并且您对 进行了编程控制器类使用接口所需的功能。

这个示例(希望)演示了接口编程如何允许您更改类的内部实现,而不必担心这些更改是否会破坏您的其他代码。

Traits

另一方面, Traits 只是一种重用代码的方法。接口不应被视为特征的相互排斥的替代品。事实上,创建满足接口所需功能的特征是理想的用例

仅当多个类共享相同的功能(可能由同一接口指定)时,才应使用特征。使用特征为单个类提供功能是没有意义的:这只会混淆该类的功能,更好的设计会将特征的功能转移到相关类中。

考虑以下特征实现:

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!';
    }
}

一个更具体的示例:想象一下接口讨论中的 FileCacherApcCacher 使用相同的方法来确定缓存条目是否过时,并且应该被删除(显然现实生活中不是这种情况,但就这样吧)。您可以编写一个特征并允许两个类使用它来满足公共接口要求。

最后提醒一句:小心不要过度使用特质。当独特的类实现就足够时,特征通常被用作糟糕设计的拐杖。您应该限制特征以满足最佳代码设计的接口要求。

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:

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).

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 want to implement a caching system for a web application to cut
down on server load

You start out by writing a class to cache request responses using 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:

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:

// 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.

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:

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.

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.

浮生面具三千个 2025-01-11 05:10:13

接口定义了实现类必须实现的一组方法。

当一个特征被使用时,方法的实现也会随之而来——这不会发生在接口中。

这是最大的区别。

来自 PHP RFC 的水平重用

Traits 是单继承语言(例如 PHP)中代码重用的机制。 Trait 旨在通过使开发人员能够在不同类层次结构中的多个独立类中自由重用方法集来减少单继承的一些限制。

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 an 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.

岁吢 2025-01-11 05:10:13

trait 本质上是 PHP 对 mixin 的实现,并且实际上是一组扩展方法,可以通过添加 trait 将其添加到任何类中代码>.然后,这些方法将成为该类实现的一部分,但不使用继承

来自 PHP 手册(强调我的):

Traits 是单继承语言(例如 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 定义的方法已经具有方法主体 - 而 Interface 定义的方法却没有。

此外 - 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 方法,后者返回重写继承的方法。

因此 - 考虑以下场景:

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 实例时,会发生以下情况:

  1. Interface IBase 需要一个名为 SomeMethod() 的无参数函数代码> 需提供。
  2. 基类BaseClass提供了该方法的实现——满足需要。
  3. trait myTrait 还提供了一个名为 SomeMethod() 的无参数函数,优先BaseClass-version
  4. class MyClass 提供了自己的 SomeMethod() 版本 - 优先超过 trait 版本。

结论

  1. 接口不能提供方法体的默认实现,而特征可以。
  2. 接口是一种多态继承构造,而特征则不然。
  3. 多个Interface可以在同一个类中使用,多个trait也可以。

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.

From the PHP Manual (emphasis mine):

Traits are a mechanism for code reuse in 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.

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.

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:

                      -----------------------------------------------
                      |   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 extends SomeBaseClass, MyClass is an 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.

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.

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:

  1. The Interface IBase requires a parameterless function called SomeMethod() to be provided.
  2. The base class BaseClass provides an implementation of this method - satisfying the need.
  3. The trait myTrait provides a parameterless function called SomeMethod() as well, which takes precedence over the BaseClass-version
  4. The class MyClass provides its own version of SomeMethod() - which takes precedence over the trait-version.

Conclusion

  1. An Interface can not provide a default implementation of a method body, while a trait can.
  2. An Interface is a polymorphic, inherited construct - while a trait is not.
  3. Multiple Interfaces can be used in the same class, and so can multiple traits.
深白境迁sunset 2025-01-11 05:10:13

我认为 traits 对于创建包含可用作多个不同类的方法的方法的类很有用。

例如:

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}

您可以在任何使用此特征的类中拥有并使用此“错误”方法。

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
    }
}

而使用接口时,您只能声明方法签名,而不能声明其函数的代码。此外,要使用接口,您需要遵循层次结构,使用 implements。特质的情况并非如此。

这是完全不同的!

I think traits are useful to create classes that contain methods that can be used as methods of several different classes.

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 uses this 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.

It is completely different!

淑女气质 2025-01-11 05:10:13

对于初学者来说,上面的答案可能很困难,这是理解它的最简单方法:

Traits

trait SayWorld {
    public function sayHello() {
        echo 'World!';
    }
}

所以如果你想在其他类中拥有 sayHello 函数而不重新创建整个函数你可以使用特质,

class MyClass{
  use SayWorld;

}

$o = new MyClass();
$o->sayHello();

酷吧!

不仅是函数,您还可以使用特征中的任何内容(函数、变量、常量...)。另外,您可以使用多个特征: use SayWorld, AnotherTraits;

Interface

  interface SayWorld {
     public function sayHello();
  }

  class MyClass implements SayWorld { 
     public function sayHello() {
        echo 'World!';
     }
}

这就是接口与特征的不同之处:您必须在已实现的接口中重新创建接口中的所有内容。班级。接口没有实现,接口只能有函数和常量,不能有变量。

我希望这有帮助!

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,

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;

Interface

  interface SayWorld {
     public function sayHello();
  }

  class MyClass implements SayWorld { 
     public function sayHello() {
        echo 'World!';
     }
}

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!

没有心的人 2025-01-11 05:10:13

特征只是为了代码重用

接口仅提供要在类中定义的函数的签名,可以根据程序员的判断来使用它。从而为我们提供了一组类原型

供参考-
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

心病无药医 2025-01-11 05:10:13

描述 Traits 的一个常用比喻是 Traits 是带有实现的接口。

在大多数情况下,这是一种很好的思考方式,但两者之间存在许多细微的差异。

首先,instanceof 运算符不适用于特征(即特征不是真实的对象),因此您不能使用它来查看类是否具有特定特征(或看看两个不相关的类是否共享一个特征)。这就是他们所说的水平代码重用结构的含义。

PHP 中现在有一些函数可以让您获取类使用的所有特征的列表,但是特征继承意味着您需要进行递归检查以可靠地检查某个类是否在某个时刻有一个特定的特征(PHP doco 页面上有示例代码)。但是,是的,它肯定不像 instanceof 那样简单和干净,而且恕我直言,它是一个可以让 PHP 变得更好的功能。

此外,抽象类仍然是类,因此它们不能解决与多重继承相关的代码重用问题。请记住,您只能扩展一个类(真实的或抽象的),但可以实现多个接口。

我发现特征和接口非常适合一起使用来创建伪多重继承。例如:

class SlidingDoor extends Door implements IKeyed  
{  
    use KeyedTrait;  
    [...] // Generally not a lot else goes here since it's all in the trait  
}

这样做意味着您可以使用 instanceof 来确定特定的 Door 对象是否是 Keyed 的,您知道您将获得一组一致的方法等,并且所有代码都在一个地方跨所有使用 KeyedTrait 的类。

An often-used metaphor to describe Traits is Traits are interfaces with implementation.

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:

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.

━╋う一瞬間旳綻放 2025-01-11 05:10:13

基本上,您可以将特征视为代码的自动“复制粘贴”。

使用特征是危险的,因为在执行之前无法知道它的作用。

然而,特征由于缺乏继承等限制而更加灵活。

特征对于注入一个方法非常有用,该方法可以检查类中的某些内容,例如另一个方法或属性是否存在。 一篇关于此的好文章(但是是法语,抱歉)

对于读法语的人来说,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.

梦里寻她 2025-01-11 05:10:13

如果您懂英语并且知道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 typing 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.

月下伊人醉 2025-01-11 05:10:13

其他答案很好地解释了接口和特征之间的差异。我将重点关注一个有用的现实世界示例,特别是演示特征可以使用实例变量的示例 - 允许您使用最少的样板代码向类添加行为。

同样,就像其他人提到的那样,特征与接口很好地配对,允许接口指定行为契约,并让特征来实现实现。

在某些代码库中,向类添加事件发布/订阅功能可能是常见场景。有 3 种常见的解决方案:

  1. 使用事件发布/订阅代码定义基类,然后想要提供事件的类可以扩展它以获得功能。
  2. 定义一个带有事件发布/订阅代码的类,然后其他想要提供事件的类可以通过组合使用它,定义自己的方法来包装组合对象,代理对它的方法调用。
  3. 使用事件发布/订阅代码定义一个特征,然后想要提供事件的其他类可以使用该特征(也称为导入它)来获得功能。

每项效果如何?

#1 效果不佳。会的,直到有一天您意识到您无法扩展基类,因为您已经扩展了其他内容。我不会展示这样的示例,因为使用这样的继承的限制应该是显而易见的。

#2 & #3 两者都运作良好。我将展示一个突出显示一些差异的示例。

首先,两个示例之间的一些代码是相同的:

一个接口

interface Observable {
    function addEventListener($eventName, callable $listener);
    function removeEventListener($eventName, callable $listener);
    function removeAllEventListeners($eventName);
}

和一些演示用法的代码:

$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());
}

好的,现在让我们展示使用特征时 Auction 类的实现有何不同。

首先,#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);
    }
}

#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]);
    }
}

请注意,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:

  1. 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.
  2. 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.
  3. 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.

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

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.

First, here's how #2 (using composition) would look like:

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:

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.

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.

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.

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 :)

土豪我们做朋友吧 2025-01-11 05:10:13

主要区别在于,对于接口,您必须在实现所述接口的每个类中定义每个方法的实际实现,因此您可以让许多类实现相同的接口但具有不同的行为,而特征只是注入的代码块一个班级;另一个重要的区别是特征方法只能是类方法或静态方法,这与接口方法不同,接口方法也可以(通常是)实例方法。

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.

烈酒灼喉 2025-01-11 05:10:13

该特征与我们可以用于多重继承目的和代码可重用性的类相同。

我们可以在类中使用特征,也可以通过“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

挖鼻大婶 2025-01-11 05:10:13

接口是一个契约,它规定“这个对象能够做这件事”,而特征则赋予对象做这件事的能力。

特征本质上是一种在类之间“复制和粘贴”代码的方法。

尝试阅读这篇文章,什么是 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?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文