私は最近PHPについて勉強しようとしていますが、自分が特性に夢中になっていることに気づきました。水平方向のコードの再利用の概念を理解しており、必ずしも抽象クラスから継承したくない。私が理解していないのは、トレイトとインターフェイスの重要な違いは何ですか?
どちらを使用するかを説明する適切なブログ投稿または記事を検索してみましたが、これまでに見つけた例は、まったく同じように非常に似ているようです。
Imagick
で、特性の前に必要だったすべての膨満感が減ります。
私は最近PHPについて勉強しようとしていますが、自分が特性に夢中になっていることに気づきました。水平方向のコードの再利用の概念を理解しており、必ずしも抽象クラスから継承したくない。私が理解していないのは、トレイトとインターフェイスの重要な違いは何ですか?
どちらを使用するかを説明する適切なブログ投稿または記事を検索してみましたが、これまでに見つけた例は、まったく同じように非常に似ているようです。
Imagick
で、特性の前に必要だったすべての膨満感が減ります。
回答:
インターフェースは、実装クラスが実装しなければならない一連のメソッドを定義します。
特性がuse
'd'になると、メソッドの実装も実行されます-これはで発生しませんInterface
。
それが最大の違いです。
トレイトは、PHPなどの単一継承言語でコードを再利用するためのメカニズムです。特性は、開発者が異なるクラス階層にあるいくつかの独立したクラスでメソッドのセットを自由に再利用できるようにすることで、単一継承のいくつかの制限を減らすことを目的としています。
公共サービスの案内:
私は記録のために、特性はほとんど常にコードのにおいであり、構成を支持するために避けられるべきであると信じていることを述べたいです。単一の継承はアンチパターンであるという点まで頻繁に悪用され、複数の継承はこの問題を悪化させるだけだと私は考えています。ほとんどの場合、継承よりも構成を優先することで(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!';
}
}
より具体的な例:あなたFileCacher
とApcCacher
インターフェイスの議論の両方が同じ方法を使用して、キャッシュエントリが古くて削除する必要があるかどうかを判断するとします(明らかに、これは実際には当てはまりませんが、それで十分です)。特性を記述して、両方のクラスが共通のインターフェース要件のためにそれを使用できるようにすることができます。
警告の最後の言葉:特性を使いすぎないように注意してください。多くの場合、特性は、一意のクラス実装で十分な貧弱な設計の松葉杖として使用されます。最良のコード設計のためには、特性をインターフェース要件を満たすように制限する必要があります。
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()
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[] 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のインスタンスを作成すると、次のようになります。
Interface
IBase
は、パラメータなしの関数が必要SomeMethod()
です。BaseClass
は、このメソッドの実装を提供します-ニーズを満たします。trait
myTrait
呼ばれるパラメータのない機能を提供SomeMethod()
だけでなく、優先される上BaseClass
-versionをclass
MyClass
独自のバージョンを提供SomeMethod()
- 優先される上trait
-versionを。結論
Interface
一方で、メソッド本体のデフォルトの実装を提供することができないtrait
ことができます。Interface
は、多相で継承された構造です-atrait
はそうではありません。Interface
同じクラスで複数のを使用できるため、複数のも使用できますtrait
。mixin
そして、私が私の回答の冒頭を再検討したので、これを反映するように更新しました。コメントしてくれてありがとう、@ BoltClock!
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_integer
、IntegerCast
インターフェイスに含まれる可能性が高くなります。
use Toolkit
あなたの代わりに$this->toolkit = new Toolkit();
、特性自体のいくつかの利点を失う可能性がありますか?
Something
実行しますif(!$something->do_something('foo')) var_dump($something->errors);
上記の初心者にとって、答えは難しいかもしれませんが、これはそれを理解する最も簡単な方法です:
特徴
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のみを持つことができ、変数を持つことはできません。
これが役に立てば幸いです!
特性を記述するためによく使用されるメタファーは、特性は実装とのインターフェースです。
これはほとんどの状況でそれについて考える良い方法ですが、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か所に配置されます。
トレイトは単にコードを再利用するためのものです。
インターフェースは、プログラマーの裁量に応じて使用できるクラスで定義される関数のシグニチャーを提供するだけです。したがって、クラスのグループのプロトタイプを提供します。
参考のために-http ://www.php.net/manual/en/language.oop5.traits.php
基本的に、トレイトはコードの自動化された「コピーと貼り付け」と考えることができます。
実行前にそれが何をするかを知る手段がないので、特性を使用することは危険です。
ただし、継承などの制限がないため、特性はより柔軟です。
トレイトは、クラスに何かをチェックするメソッド(たとえば、別のメソッドまたは属性の存在)を注入するのに役立ちます。それに関する素晴らしい記事(しかしフランス語で申し訳ありません)。
それを入手できるフランス語を読む人のために、GNU / Linux Magazine HS 54はこの主題に関する記事を持っています。
他の回答は、インターフェースと特性の違いを説明するのに非常に役立ちました。ここでは、有用な実例に焦点を当てます。特に、トレイトがインスタンス変数を使用できることを示しているため、最小限のボイラープレートコードでクラスに動作を追加できます。
繰り返しになりますが、他の人が述べたように、トレイトはインターフェースと適切にペアになり、インターフェースが動作コントラクトを指定し、トレイトが実装を実行できるようにします。
イベント発行/サブスクライブ機能をクラスに追加することは、一部のコードベースでは一般的なシナリオです。3つの一般的な解決策があります。
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]);
}
}
内のコードEventEmitterTrait
はEventEmitter
、トレイトがtriggerEvent()
メソッドを保護されていると宣言することを除いて、クラス内のコードとまったく同じであることに注意してください。したがって、確認する必要がある唯一の違いは、Auction
クラスの実装です。
そしてその違いは大きい。コンポジションを使用すると、優れたソリューションが得られ、EventEmitter
好きなだけ多くのクラスで再利用できます。ただし、主な欠点は、Observable
インターフェイスで定義された各メソッドに対して、実装して、対応するメソッドに引数を転送するだけの退屈なボイラープレートコードを作成する必要があるため、作成して維持する必要があるボイラープレートコードがたくさんあることです。EventEmitter
オブジェクトを構成しました。この例の特性を使用すると、それを回避でき、定型コードを削減して保守性を向上させることができます。
ただし、Auction
クラスに完全なObservable
インターフェースを実装させたくない場合があります。1つまたは2つのメソッドのみを公開したい場合や、独自のメソッドシグネチャを定義できるようにまったく公開したくない場合もあります。そのような場合でも、合成方法を好むかもしれません。
ただし、この特性はほとんどのシナリオで非常に魅力的です。特に、インターフェースに多数のメソッドがある場合は、定型文を大量に作成することになります。
*実際に両方を実行することもできます- EventEmitter
構成的に使用する場合に備えてクラスを定義し、EventEmitterTrait
特性EventEmitter
内のクラス実装を使用して特性も定義します。
トレイトは、多重継承とコードの再利用性のために使用できるクラスと同じです。
クラス内で特性を使用できます。また、「キーワードを使用」を使用して、同じクラスで複数の特性を使用できます。
インターフェースは、特性と同じコードの再利用性のために使用しています
インターフェースは複数のインターフェースを拡張したものなので、複数の継承の問題を解決できますが、インターフェースを実装するときは、クラス内にすべてのメソッドを作成する必要があります。詳細については、以下のリンクをクリックしてください。
http://php.net/manual/en/language.oop5.traits.php http://php.net/manual/en/language.oop5.interfaces.php
インターフェースは「このオブジェクトはこのことを実行できる」というコントラクトですが、トレイトはオブジェクトにそのことを実行する能力を与えます。
特性は本質的に、クラス間でコードを「コピーして貼り付ける」方法です。
主な違いは、インターフェースでは、そのインターフェースを実装する各クラス内の各メソッドの実際の実装を定義する必要があるため、特性は注入されたコードのチャンクでありながら、同じインターフェースを異なる動作で実装することができます。クラス; 別の重要な違いは、トレイトメソッドはクラスメソッドまたは静的メソッドにしかできないことです。