特性とインターフェース


344

私は最近PHPについて勉強しようとしていますが、自分が特性に夢中になっていることに気づきました。水平方向のコードの再利用の概念を理解しており、必ずしも抽象クラスから継承したくない。私が理解していないのは、トレイトとインターフェイスの重要な違いは何ですか?

どちらを使用するかを説明する適切なブログ投稿または記事を検索してみましたが、これまでに見つけた例は、まったく同じように非常に似ているようです。


6
インターフェイスの関数本体にはコードがありません。実際には関数本体はありません。
hakre

2
私の大いに賛成された答えにもかかわらず、私はそれが私が一般に反特性/ミックスインであるという記録のためにそれを述べたいです。このチャットのトランスクリプトをチェックして、どのようにして多くの特性が確実なOOPプラクティスを損なうのかを確認してください。
rdlowrey、

2
反対だと思います。何年も前から、そして特性の出現以来、PHPを使用してきたので、その価値を証明するのは簡単だと思います。「イメージモデル」がオブジェクトのように歩き、話すことを可能にするこの実用的な例を読むだけImagickで、特性の前に必要だったすべての膨満感が減ります。
quickshiftin 2014年

特性とインターフェースは似ています。主な違いは、トレイトではメソッドを実装できるが、インターフェースでは実装できないことです。
John

回答:


238

インターフェースは、実装クラス実装しなければならない一連のメソッドを定義します。

特性がuse'd'になると、メソッドの実装も実行されます-これはで発生しませんInterface

それが最大の違いです。

PHP RFCのHorizo​​ntal Reuseから:

トレイトは、PHPなどの単一継承言語でコードを再利用するためのメカニズムです。特性は、開発者が異なるクラス階層にあるいくつかの独立したクラスでメソッドのセットを自由に再利用できるようにすることで、単一継承のいくつかの制限を減らすことを目的としています。


2
@JREAM実際には、何もありません。実際には、はるかに。
アレック渓谷

79
ただし、トレイトはインターフェイスではありません。インターフェースは、チェックできる仕様です。特性はチェックできないため、実装のみです。これらは、インターフェースの正反対です。RFCのその行は単に間違っています...
ircmaxell

195
特性は、基本的に言語支援のコピーと貼り付けです。
Shahid

10
それは比喩ではありません。それは言葉の意味を解体することです。これは、ボックスをボリュームのあるサーフェスとして説明するようなものです。
12

6
ircmaxellとShadiのコメントを拡張するには:オブジェクトが(instanceofを介して)インターフェイスを実装しているかどうかを確認でき、メソッドの引数がメソッドシグネチャの型ヒントを介してインターフェイスを実装していることを確認できます。特性の対応するチェックを実行することはできません。
Brian D'Astous 2013

530

公共サービスの案内:

私は記録のために、特性はほとんど常にコードのにおいであり、構成を支持するために避けられるべきであると信じていることを述べたいです。単一の継承はアンチパターンであるという点まで頻繁に悪用され、複数の継承はこの問題を悪化させるだけだと私は考えています。ほとんどの場合、継承よりも構成を優先することで(1つでも複数でも)、はるかに良い結果が得られます。特性とインターフェースとの関係にまだ興味がある場合は、以下をお読みください...


まず、次のように言ってみましょう。

オブジェクト指向プログラミング(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ようにControllerクラスをプログラムします。

この例は、(うまくいけば)インターフェイスへのプログラミングによって、変更が他のコードに影響を与えるかどうか心配することなく、クラスの内部実装を変更できる方法を示しています。

特徴

一方、トレイトは単にコードを再利用する方法です。インターフェースは、トレイトの相互に排他的な代替として考えられるべきではありません。実際、インターフェースが必要とする機能を満たす特性を作成することは、理想的なユースケースです。

トレイトは、複数のクラスが同じ機能を共有している場合にのみ使用する必要があります(同じインターフェイスによって指示される可能性があります)。単一のクラスに機能を提供するために特性を使用する意味はありません。それは、クラスの機能を難読化するだけであり、優れた設計は、特性の機能を関連するクラスに移動します。

次のトレイトの実装を検討してください。

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インターフェイスの議論の両方が同じ方法を使用して、キャッシュエントリが古くて削除する必要があるかどうかを判断するとします(明らかに、これは実際には当てはまりませんが、それで十分です)。特性を記述して、両方のクラスが共通のインターフェース要件のためにそれを使用できるようにすることができます。

警告の最後の言葉:特性を使いすぎないように注意してください。多くの場合、特性は、一意のクラス実装で十分な貧弱な設計の松葉杖として使用されます。最良のコード設計のためには、特性をインターフェース要件を満たすように制限する必要があります。


69
私は本当に上記で提供された簡単な簡単な答えを探していましたが、他の人の区別を明確にするのに役立つ優れた詳細な答えを与えたと言わなければなりません。
datguywhowanders

35
「[C]特定のクラスのインターフェースが必要とする機能を満たす特性を作成することは、理想的なユースケースです。」正確:+1
アレック渓谷

5
PHPの特性は他の言語のミックスインに似ていると言ってもいいでしょうか?
イーノ、

5
@igorpanすべての意図と目的で、PHPのトレイトの実装多重継承と同じだと思います。PHPの特性が静的プロパティを指定している場合、その特性を使用する各クラスは、静的プロパティの独自のコピーを持つことに注意してください。さらに重要なことは、トレイトを照会するときにこの投稿がSERPで非常に高くなっていることを確認して、ページの上部に公共サービスのお知らせを追加します。あなたはそれを読むべきです。
rdlowrey 2013

3
詳細な説明については+1。私はルビーの出身で、ミックスインがたくさん使われています。私の2セントを追加するだけで、私たちが使用する良い経験則は、「特性で$ thisを変更するメソッドを実装しないでください」とphpで変換できます。これにより、おかしなデバッグセッションの束全体を防ぐことができます...ミックスインは、ミックスインされるクラスについても想定しないでください(または、非常に明確にして、依存関係を最小限に抑える必要があります)。この点で、インターフェイスを実装するトレイトのアイデアは素晴らしいと思います。
m_x 2013

67

A traitは本質的にPHPのの実装でありmixin、事実上、を追加することで任意のクラスに追加できる拡張メソッドのセットですtrait。その後、メソッドはそのクラスの実装の一部になりますが、継承は使用しません

PHPマニュアル(強調鉱山):

トレイトは、PHPなどの単一継承言語でコードを再利用するためのメカニズムです。...これは、伝統的な継承への追加であり、動作の水平構成を可能にします。つまり、継承を必要としないクラスメンバーのアプリケーションです。

例:

trait myTrait {
    function foo() { return "Foo!"; }
    function bar() { return "Bar!"; }
}

上記の特性を定義すると、次のことができるようになります。

class MyClass extends SomeBaseClass {
    use myTrait; // Inclusion of the trait myTrait
}

この時点で、クラスのインスタンスを作成するとMyClass、とから呼び出される2つのメソッドがあり、から取得されます。また、-definedメソッドにはすでにメソッド本体があることに注意してください--definedメソッドにはありません。foo()bar()myTraittraitInterface

さらに、PHPは他の多くの言語と同様に、単一の継承モデルを使用します。つまり、クラスは複数のインターフェースから派生できますが、複数のクラスからは派生できません。ただし、PHPクラスには複数のインクルージョンを含めることできます。traitこれにより、プログラマは複数の基本クラスを含める場合と同様に、再利用可能な部分を含めることができます。

注意すべきいくつかの点:

                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------

ポリモーフィズム:

前述の例では、ここMyClass に延び SomeBaseClassMyClass あるインスタンスSomeBaseClass。つまり、などの配列にSomeBaseClass[] basesはのインスタンスを含めることができますMyClass。同様に、MyClass拡張した場合IBaseInterface、の配列にIBaseInterface[] basesはのインスタンスを含めることができますMyClass。aで利用できるそのようなポリモーフィックなコンストラクトはありません。traitなぜなら、a 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-versionを。

結論

  1. Interface一方で、メソッド本体のデフォルトの実装を提供することができないtraitことができます。
  2. An Interfaceは、多相継承された構造です-atraitはそうではありません。
  3. Interface同じクラスで複数のを使用できるため、複数のも使用できますtrait

4
「特性は、抽象クラスのC#の概念に似ています」いいえ、抽象クラスは抽象クラスです。この概念は、PHPとC#の両方に存在します。代わりに、PHPの特性をC#の拡張メソッドで構成される静的クラスと比較します。1つの型のみを拡張する拡張メソッドとは異なり、特性はほとんどすべての型で使用できるため、型ベースの制限が削除されます。
BoltClock

1
非常に良い解説-そして私はあなたに同意します。再読すると、それはより良いアナロジーです。しかし、それをaと考える方が良いと思います。mixinそして、私が私の回答の冒頭を再検討したので、これを反映するように更新しました。コメントしてくれてありがとう、@ BoltClock!
Troy Alford、2014年

1
c#拡張メソッドとは関係ないと思います。拡張メソッドは、単一のクラス型に追加されます(もちろんクラス階層を尊重します)。その目的は、型を追加機能で拡張することであり、複数のクラスで「コードを共有」して混乱させることではありません。比較できない!何かを再利用する必要がある場合、それは通常、それが独自のスペースを持つ必要があることを意味します。たとえば、共通の機能を必要とするクラスに関連する個別のクラスなどです。実装はデザインによって異なりますが、大体それだけです。特性は、貧弱なコードを作成するためのもう1つの方法です。
ソフィア2015年

クラスは複数のインターフェースを持つことができますか?グラフが間違っているかどうかはわかりませんが、クラスXはY、Zを実装していますが有効です。
Yann Chabot 2016

26

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

を使用interfacesすると、メソッドシグネチャのみを宣言でき、その関数のコードは宣言できません。また、インターフェースを使用するには、を使用して階層をたどる必要がありますimplements。これは、特性には当てはまりません。

全然違います!


これは特性の悪い例だと思います。クラスを(インテリジェントに)整数にキャストする基本的に同様の方法がないためto_integerIntegerCastインターフェイスに含まれる可能性が高くなります。
マシュー

5
「to_integer」を忘れてください-それは単なるイラストです。例。「こんにちは、世界」。「example.com」。
J. Bruni、

2
このツールキットトレイトには、スタンドアロンユーティリティクラスでは実現できなかったメリットがありますか?use Toolkitあなたの代わりに$this->toolkit = new Toolkit();、特性自体のいくつかの利点を失う可能性がありますか?
アンソニー

@AnthonyはのコンテナのどこかでSomething実行しますif(!$something->do_something('foo')) var_dump($something->errors);
TheRealChx101 2015

20

上記の初心者にとって、答えは難しいかもしれませんが、これはそれを理解する最も簡単な方法です:

特徴

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

したがって、sayHello関数全体を再作成せずに他のクラスで関数を使用したい場合は、特性を使用できます。

class MyClass{
  use SayWorld;

}

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

かっこいい!

関数だけでなく、trait(function、variables、const ..)で何でも使用できます。また、複数の特性を使用できます。use SayWorld,AnotherTraits;

インターフェース

  interface SayWorld {
     public function sayHello();
  }

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

したがって、これはインターフェイスとトレイトの違いです。実装されたクラスのインターフェイスですべてを再作成する必要があります。インターフェースには実装がありません。インターフェースは関数とconstのみを持つことができ、変数を持つことはできません。

これが役に立てば幸いです!


5

特性を記述するためによく使用されるメタファーは、特性は実装とのインターフェースです。

これはほとんどの状況でそれについて考える良い方法ですが、2つの間にはいくつかの微妙な違いがあります。

はじめに、instanceofオペレーターはトレイトを操作しません(つまり、トレイトは実際のオブジェクトではありません)。そのため、クラスに特定のトレイトがあるかどうか(または、2つの他の無関係なクラスがトレイトを共有しているかどうかを確認すること)はできません。 )。それが、水平方向のコードを再利用するための構成要素であることの意味です。

PHPには現在、クラスが使用するすべての特性のリストを取得できる関数があります、特性継承は、ある時点でクラスに特定の特性があるかどうかを確実にチェックするために再帰チェックを行う必要があることを意味します(例があります) PHP docoページのコード)。しかし、そうです、それは確かにinstanceofほど単純でクリーンではありません。IMHOは、PHPをより良くする機能です。

また、抽象クラスは依然としてクラスであるため、多重継承に関連するコードの再利用の問題は解決されません。拡張できるのは1つのクラス(実または抽象)だけで、複数のインターフェースを実装できることを覚えておいてください。

特性とインターフェースは、疑似多重継承を作成するために連携して使用するのに非常に適していることがわかりました。例えば:

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を使用するすべてのクラスにわたってすべてのコードが1か所に配置されます。


もちろん、その回答の最後の部分は、@ rdlowreyが投稿の「特性」の最後の3つの段落で詳しく言っていることです。本当にシンプルなスケルトンコードスニペットがそれを説明するのに役立つと感じました。
Jon Kloske、2012

特性を使用するOOの最良の方法は、可能な場合はインターフェースを使用することだと思います。そして、そこに複数のサブクラスは、そのインターフェイスのために同じ種類のコードを実装する場合があり、あなたはその(抽象)スーパークラスにそのコードを移動できない場合は- >の特性とそれを実装する
プレイヤー-1

4

トレイトは単にコードを再利用するためのものです

インターフェースは、プログラマーの裁量に応じて使用できるクラス定義される関数のシグニチャーを提供するだけです。したがって、クラスのグループのプロトタイプを提供します

参考のために-http ://www.php.net/manual/en/language.oop5.traits.php


3

基本的に、トレイトはコードの自動化された「コピーと貼り付け」と考えることができます。

実行前にそれが何をするかを知る手段がないので、特性を使用することは危険です。

ただし、継承などの制限がないため、特性はより柔軟です。

トレイトは、クラスに何かをチェックするメソッド(たとえば、別のメソッドまたは属性の存在)を注入するのに役立ちます。それに関する素晴らしい記事(しかしフランス語で申し訳ありません)

それを入手できるフランス語を読む人のために、GNU / Linux Magazine HS 54はこの主題に関する記事を持っています。


それでも、特性がデフォルト実装のインターフェースとどのように異なるか理解しないでください
denis631

@ denis631特性はコードのスニペットとして、インターフェースは署名コントラクトとして見ることができます。それが役立つ場合は、何でも含むことができるクラスの非公式なビットとして見ることができます。それが役立つかどうか私に知らせてください。
ベンジ2018

PHPトレイトは、コンパイル時に展開されるマクロと見なすことができます。つまり、そのコードスニペットにそのキーのエイリアスを付けるだけです。しかし、錆の特性は異なって見えます(または私は間違っています)。しかし、どちらも言葉の特徴を持っているので、私はそれらが同じ、つまり同じ概念を意味すると仮定します。Rustトレイトのリンク:doc.rust-lang.org/rust-by-example/trait.html
denis631

2

あなたが英語を知っていてtrait、その意味がわかっているなら、それはまさにその名前が言っていることです。これは、と入力して既存のクラスにアタッチするメソッドとプロパティのクラスレスパックですuse

基本的には、単一の変数と比較できます。クロージャー関数は、useこれらの変数をスコープの外から、そしてそのようにしてそれらが内部に値を持つことができます。それらは強力であり、すべてで使用できます。それらが使用されている場合、同じことが特性に起こります。


2

他の回答は、インターフェースと特性の違いを説明するのに非常に役立ちました。ここでは、有用な実例に焦点を当てます。特に、トレイトがインスタンス変数を使用できることを示しているため、最小限のボイラープレートコードでクラスに動作を追加できます。

繰り返しになりますが、他の人が述べたように、トレイトはインターフェースと適切にペアになり、インターフェースが動作コントラクトを指定し、トレイトが実装を実行できるようにします。

イベント発行/サブスクライブ機能をクラスに追加することは、一部のコードベースでは一般的なシナリオです。3つの一般的な解決策があります。

  1. イベントパブ/サブコードを使用して基本クラスを定義し、イベントを提供するクラスがそれを拡張して機能を取得できます。
  2. イベントのpub / subコードでクラスを定義すると、イベントを提供したい他のクラスがコンポジションを介してそれを使用し、独自のメソッドを定義して、合成されたオブジェクトをラップし、メソッド呼び出しをプロキシすることができます。
  3. イベントpub / subコードで特性を定義すると、イベントを提供したい他のクラスがuse特性(別名:インポート)を使用して機能を取得できます。

それぞれうまくいくのですか?

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

内のコードEventEmitterTraitEventEmitter、トレイトがtriggerEvent()メソッドを保護されていると宣言することを除いて、クラス内のコードとまったく同じであることに注意してください。したがって、確認する必要がある唯一の違いは、Auctionクラスの実装です

そしてその違いは大きい。コンポジションを使用すると、優れたソリューションが得られ、EventEmitter好きなだけ多くのクラスで再利用できます。ただし、主な欠点は、Observableインターフェイスで定義された各メソッドに対して、実装して、対応するメソッドに引数を転送するだけの退屈なボイラープレートコードを作成する必要があるため、作成して維持する必要があるボイラープレートコードがたくさんあることです。EventEmitterオブジェクトを構成しました。この例の特性を使用すると、それを回避でき定型コードを削減して保守性を向上させることができます

ただし、Auctionクラスに完全なObservableインターフェースを実装させたくない場合があります。1つまたは2つのメソッドのみを公開したい場合や、独自のメソッドシグネチャを定義できるようにまったく公開したくない場合もあります。そのような場合でも、合成方法を好むかもしれません。

ただし、この特性はほとんどのシナリオで非常に魅力的です。特に、インターフェースに多数のメソッドがある場合は、定型文を大量に作成することになります。

*実際に両方を実行することもできます- EventEmitter構成的に使用する場合に備えてクラスを定義し、EventEmitterTrait特性EventEmitter内のクラス実装を使用して特性も定義します。


1

トレイトは、多重継承とコードの再利用性のために使用できるクラスと同じです。

クラス内で特性を使用できます。また、「キーワードを使用」を使用して、同じクラスで複数の特性を使用できます。

インターフェースは、特性と同じコードの再利用性のために使用しています

インターフェースは複数のインターフェースを拡張したものなので、複数の継承の問題を解決できますが、インターフェースを実装するときは、クラス内にすべてのメソッドを作成する必要があります。詳細については、以下のリンクをクリックしてください。

http://php.net/manual/en/language.oop5.traits.php http://php.net/manual/en/language.oop5.interfaces.php



0

主な違いは、インターフェースでは、そのインターフェースを実装する各クラス内の各メソッドの実際の実装を定義する必要があるため、特性は注入されたコードのチャンクでありながら、同じインターフェースを異なる動作で実装することができます。クラス; 別の重要な違いは、トレイトメソッドはクラスメソッドまたは静的メソッドにしかできないことです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.