为了账号安全,请及时绑定邮箱和手机立即绑定

使用特征为接口定义函数的目的是什么

使用特征为接口定义函数的目的是什么

PHP
米脂 2022-09-03 16:16:38
抱歉,如果这是一个重复的问题或一个共同的设计原则,我已经搜索了一下,但无法找到这个问题的任何答案。我可能只是用错误的关键字搜索。我一直在看一个流行的库Sabre/Event(https://sabre.io/event/),在代码中有一个简单的类/继承模型,我试图理解:类 EventEmitter 实现 EventEmitterInterface 并使用 EventEmitterTrait(请参阅下面的代码)。EventEmitterTrait 在类上方有一条评论说:* Using the trait + interface allows you to add EventEmitter capabilities* without having to change your base-class.我试图理解为什么这个评论是这么说的,以及为什么它允许在不改变基类的情况下添加功能,以及这与将例程放入EventEmitter本身有何不同。难道您不能扩展 EventEmitter 并在派生类中添加功能吗?简化代码:// EventEmitter.phpclass EventEmitter implements EventEmitterInterface {    use EventEmitterTrait;}// EventEmitterInterface.phpinterface EventEmitterInterface {    // ... declares several function prototypes}// EventEmitterTrait.phptrait EventEmitterTrait {    // ... implements the routines declared in EventEmitterInterface}
查看完整描述

4 回答

?
Qyouu

TA贡献1786条经验 获得超11个赞

你基本上在这里问了两个问题。

  1. 什么是接口,为什么它们有用?

  2. 什么是特征,为什么它们有用?

要理解为什么接口有用,你必须对继承和OOP有所了解。如果你以前听说过意大利面条代码这个术语(当你倾向于编写如此纠缠在一起的命令式代码时,你很难理解它),那么你应该把它比作术语“烤宽面条代码”(当你将一个类扩展到如此多的层时,很难理解哪个层在做什么)。

1. 接口

接口通过允许一个类实现一组通用的方法而不必限制该类的层次结构来消除一些混淆。我们不从基类派生接口。我们只是将它们实现到一个给定的类中。

PHP中一个非常明显的例子是DateTimeInterface。它提供了一组通用的方法,这些方法既可以实现,也可以实现。但是,它不会告诉这些类实现是什么。类是一种实现。接口只是类 sans 实现的方法。但是,由于这两个东西都实现了相同的接口,因此很容易测试实现该接口的任何类,因为您知道它们将始终具有相同的方法。所以我知道两者都将实现该方法,它将接受a作为输入并返回a,无论哪个类正在实现它。我甚至可以编写我自己的实现,并保证该方法具有相同的签名。DateTimeDateTimeImmutableDateTimeDateTimeImmutableformatStringStringDateTimeDateTimeInterface

所以想象一下,我写了一个接受一个对象的方法,并且该方法期望在该对象上运行该方法。如果它不关心具体地给了它哪个类,那么该方法可以简单地将其原型键入为。现在,任何人都可以自由地在自己的类中实现,而不必从某个基类扩展,并为我的方法提供一个保证以相同方式工作的对象。DateTimeformatDateTimeInterfaceDateTimeInterface


因此,相对于您的示例,您可以将类的相同功能(如)添加到任何甚至可能无法从 扩展的类中,但只要我们知道它实现了相同的接口,我们就确信它具有具有相同签名的相同方法。对于 ..c,这意味着同样的事情。EventEmitterDateTimeDateTimeEventEmitter

2. 性状

与接口不同,Traits实际上可以提供实现。它们也是水平继承的一种形式,与扩展类的垂直继承不同。因为两个完全不同的类不是从同一个基类派生的,可以使用相同的。这是可能的,因为在PHP中,特征基本上只是编译器辅助的复制和粘贴。想象一下,您从字面上复制了一个特征内部的代码,并在编译时之前将其粘贴到使用它的每个类中。你会得到同样的结果。您只是将代码注入不相关的类中。Trait

这很有用,因为有时您有一个方法或一组方法,这些方法或方法在两个不同的类中被证明是可重用的,即使这些类的其余部分没有其他共同点。

例如,假设您正在编写一个CMS,其中有一个类和一个类。这两个类都没有以任何有意义的方式相关。他们做非常不同的事情,其中一个扩展另一个是没有意义的。但是,它们都共享一个共同的特定行为:flag() 方法,该方法指示对象已被用户标记以违反服务条款。DocumentUser

trait FlagContent {    public function flag(Int $userId, String $reason): bool {        $this->flagged = true;        $this->byUserId = $userId;        $this->flagReason = $reason;        return $this->updateDatabase();
    }
}

现在考虑一下,也许您的CMS还有其他内容可能会被标记,例如类,类,甚至类。这些类通常都是不相关的。仅仅使用一个特定的类来标记内容可能没有多大意义,例如,如果必须将相关对象的属性传递给该类以更新数据库。它们从基类派生也没有意义(它们彼此完全无关)。在每个类中重写相同的代码也没有意义,因为在一个地方而不是许多地方更改它更容易。ImageVideoComment

因此,这里似乎最明智的做法是使用 .Trait


因此,与你的示例相比,它们为您提供了一些可以在实现类中重用的特征,基本上可以更轻松地重用代码,而无需从基类(水平继承)扩展。EventEmitter


查看完整回答
反对 回复 2022-09-03
?
湖上湖

TA贡献2003条经验 获得超2个赞

根据 Sabre 的 Event Emitter 关于“集成到其他对象”的文档:

要将发射器功能添加到任何类,只需对其进行扩展即可。

如果无法扩展,因为该类已经是现有类层次结构的一部分,则可以使用提供的 trait。

因此,在这种情况下,我们的想法是,如果您使用自己的对象,这些对象已经是类层次结构的一部分,则可以简单地实现接口并使用该特征,而不是扩展发射器类(您将无法扩展)。


查看完整回答
反对 回复 2022-09-03
?
慕的地6264312

TA贡献1817条经验 获得超6个赞

这是一个有趣的问题,我将尝试给出我的看法。正如你所问的,


使用特征为接口定义函数的目的是什么?

Traits基本上使您能够创建一些可重用的代码或功能,然后可以在代码库中的任何地方使用。现在,PHP不支持多重继承,因此特征和接口可以解决这个问题。这里的问题是为什么特征虽然??想象一下下面的场景,


class User

{

  public function hasRatings()

  {

    // some how we want users to have ratings

  }


  public function hasBeenFavorited()

  {

    // other users can follow

  }


  public function name(){}

  public function friends(){}


  // and a few other methods

}

现在假设我们有一个 post 类,它与 user 具有相同的逻辑,可以通过 have 和 方法实现。现在,一种方法是简单地从用户类继承。hasRatings()hasBeenFavorited()


class Post extends User

{

  // Now we have access to the mentioned methods but we have inherited

  // methods and properties which is not really needed here

}

因此,要解决此问题,我们可以使用特征。


trait UserActions 

   {

     public function hasRatings()

      {

        // some how we want users to have ratings

      }


      public function hasBeenFavorited()

      {

        // other users can follow

      }


   }

有了这种逻辑,我们现在就可以在代码中需要它的任何地方使用它。


class User

{

  use UserActions;

}


class Post

{

  use UserActions;

}

现在假设我们有一个报告类,我们希望根据用户操作生成某些报告。


class Report 

{

   protected $user;


   public function __construct(User $user)

   {

     $this->user = $user

   }


   public function generate()

   {

     return $this->user->hasRatings();

   }

}

现在,如果我想为生成报告,会发生什么。实现这一目标的唯一方法是新建另一个报告类,即可能..你能看出我在哪里吗?当然,可能还有另一种方式,我不必重复自己。这就是接口或合同到位的地方。牢记这一点,让我们重新定义我们的报告类,并使其接受契约而不是具体类,这将始终确保我们有权访问。PostPostReportUserActions


interface UserActionable

{

   public function hasRatings();


   public function hasBeenFavorited();

}




 class Report 

    {

       protected $actionable;


       public function __construct(UserActionable $actionable)

       {

         $this->actionable = $actionable;

       }


       public function generate()

       {

         return $this->actionable->hasRatings();

       }

    }


//lets make our post and user implement the contract so we can pass them

// to report


class User implements UserActionable

{

  uses UserActions;

}


class Post implements UserActionable

{

  uses UserActions;

}


// Great now we can switch between user and post during run time to generate 

// reports without changing the code base


$userReport = (new Report(new User))->generate();

$postReport = (new Report(new Post))->generate();

简而言之,接口和特性有助于我们实现基于SOLID原则的设计,许多解耦的代码和更好的组合。希望有所帮助


查看完整回答
反对 回复 2022-09-03
?
慕田峪9158850

TA贡献1794条经验 获得超7个赞

集成到其他对象文档说:

如果无法扩展,因为该类已经是现有类层次结构的一部分,则可以使用提供的 trait”。

我知道,当您已经拥有不想更改的OOP设计并且想要添加事件功能时,这是一种解决方法。例如:

Model -> AppModel -> Customer

PHP没有多重继承,所以可以扩展,但不能同时扩展。如果你在代码中实现接口是不能在其他地方重用的;如果您实现例如 它随处可见,这可能是不可取的。CustomerAppModelEmitterCustomerAppModel

使用特征,您可以编写自定义事件代码,并挑选重用它的位置。


查看完整回答
反对 回复 2022-09-03
  • 4 回答
  • 0 关注
  • 92 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信