サーチ…
水平コードの再利用を容易にする特性
ロギング用のインターフェースがあるとしましょう:
interface Logger {
function log($message);
}
ここで、 Logger
インターフェースの2つの具体的な実装、 FileLogger
とFileLogger
としConsoleLogger
。
class FileLogger implements Logger {
public function log($message) {
// Append log message to some file
}
}
class ConsoleLogger implements Logger {
public function log($message) {
// Log message to the console
}
}
今度は、あなたがロギングタスクを実行できるようにしたい他のクラスFoo
を定義すると、次のようなことができます:
class Foo implements Logger {
private $logger;
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
public function log($message) {
if ($this->logger) {
$this->logger->log($message);
}
}
}
Foo
はLogger
にもなっていLogger
が、その機能はsetLogger()
介して渡されるLogger
実装に依存します。今度は、クラスBar
にこのロギングメカニズムも持たせたい場合は、このロジックをBar
クラスに複製する必要があります。
コードを複製する代わりに、特性を定義することができます。
trait LoggableTrait {
protected $logger;
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
public function log($message) {
if ($this->logger) {
$this->logger->log($message);
}
}
}
今度は、特性の論理を定義したので、特性を使用してFoo
クラスとBar
クラスにロジックを追加できます。
class Foo {
use LoggableTrait;
}
class Bar {
use LoggableTrait;
}
そして、例えば、次のようにFoo
クラスを使うことができます:
$foo = new Foo();
$foo->setLogger( new FileLogger() );
//note how we use the trait as a 'proxy' to call the Logger's log method on the Foo instance
$foo->log('my beautiful message');
紛争解決
1つのクラスにいくつかの特性を使用しようとすると、矛盾するメソッドを含む問題が発生する可能性があります。このような競合を手動で解決する必要があります。
たとえば、この階層を作成しましょう。
trait MeowTrait {
public function say() {
print "Meow \n";
}
}
trait WoofTrait {
public function say() {
print "Woof \n";
}
}
abstract class UnMuteAnimals {
abstract function say();
}
class Dog extends UnMuteAnimals {
use WoofTrait;
}
class Cat extends UnMuteAnimals {
use MeowTrait;
}
さて、次のクラスを作成しようとしましょう:
class TalkingParrot extends UnMuteAnimals {
use MeowTrait, WoofTrait;
}
PHPインタプリタは致命的なエラーを返します:
致命的なエラー: TalkingParrotの他の特性メソッドとの衝突があるため、Traitメソッドが適用されていません
この競合を解決するために、これを行うことができます:
- キーワードを使用
insteadof
別の特質から、代わりの方法の1つの形質からメソッドを使用します -
WoofTrait::say as sayAsDog;
ようなWoofTrait::say as sayAsDog;
使ってメソッドのエイリアスを作成しますWoofTrait::say as sayAsDog;
class TalkingParrotV2 extends UnMuteAnimals {
use MeowTrait, WoofTrait {
MeowTrait::say insteadof WoofTrait;
WoofTrait::say as sayAsDog;
}
}
$talkingParrot = new TalkingParrotV2();
$talkingParrot->say();
$talkingParrot->sayAsDog();
このコードは次の出力を生成します:
ニャー
横糸
複数の形質の使用
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo 'World';
}
}
class MyHelloWorld {
use Hello, World;
public function sayExclamationMark() {
echo '!';
}
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
上の例は次のように出力されます:
Hello World!
メソッドの可視性の変更
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
// Change visibility of sayHello
class MyClass1 {
use HelloWorld { sayHello as protected; }
}
// Alias method with changed visibility
// sayHello visibility not changed
class MyClass2 {
use HelloWorld { sayHello as private myPrivateHello; }
}
この例を実行する:
(new MyClass1())->sayHello();
// Fatal error: Uncaught Error: Call to protected method MyClass1::sayHello()
(new MyClass2())->myPrivateHello();
// Fatal error: Uncaught Error: Call to private method MyClass2::myPrivateHello()
(new MyClass2())->sayHello();
// Hello World!
したがって、 MyClass2
の最後の例では、 trait HelloWorld
からの元のエイリアスMyClass2
されたメソッドはそのままの状態でアクセス可能であることに注意してください。
形質とは何ですか?
PHPでは単一の継承しかできません。言い換えれば、クラスは他のクラスを1つだけextend
ことができます。しかし、あなたが親クラスに属していないものを含める必要がある場合はどうなりますか? PHP 5.4より前には、創造的にする必要がありましたが、5.4の特性が導入されました。 Traitを使用すると、基本的にクラスの一部をメインクラスにコピー&ペーストすることができます
trait Talk {
/** @var string */
public $phrase = 'Well Wilbur...';
public function speak() {
echo $this->phrase;
}
}
class MrEd extends Horse {
use Talk;
public function __construct() {
$this->speak();
}
public function setPhrase($phrase) {
$this->phrase = $phrase;
}
}
だからここに私たちはすでにHorse
伸ばしているMrEd
を持っています。しかし、すべての馬がTalk
わけではないので、私たちはそのための特質を持っています。これが何をしているのかを見てみましょう
最初に、我々は我々の特性を定義する。オートローディングと名前空間でそれを使うことができます(名前空間のクラスや関数の参照も参照してください)。それから、キーワードuse
MrEd
クラスにMrEd
use
。
MrEd
はTalk
関数と変数を定義せずに使用することに注意してください。 コピー&ペーストについて私たちが言ったことを覚えていますか?これらの関数と変数は、クラス内で定義されているかのように、クラス内ですべて定義されています。
形質は、変数と関数を定義できる点で抽象クラスに最も密接に関連しています。また、Traitを直接インスタンス化することもできません( new Trait()
)。 Traitは、クラスが暗黙的にAbstractクラスやInterface canのような関数を定義するよう強制することはできません。特性は明示的な定義のためのものです(あなたが望むだけ多くのインタフェースをimplement
できるので、 インタフェースを参照してください)。
私はいつ特性を使うべきですか?
あなたがTraitを考えるときにまずやるべきことは、この重要な質問を自分自身にすることです
コードを再構成して特性を使用することを避けることはできますか?
多くの場合、答えはイエスになるでしょう。形質とは、単一継承によって引き起こされるエッジケースです。形質を誤用したり、過剰に使用したりする誘惑は高くなる可能性があります。しかし、Traitがあなたのコードの別のソースを紹介していると考えてください。つまり、別の複雑さがあります。ここの例では、3つのクラスのみを扱っています。しかし、特性は、あなたが今それ以上のものを扱うことができることを意味します。それぞれのTraitを定義するために、それぞれのTraitを参照する必要があります(また、衝突が発生した場所、衝突の解決方法などを参照してください)。理想的には、できるだけコード内に少数の特性を保持する必要があります。
クラスを清潔に保つための特性
時間の経過とともに、私たちのクラスはますます多くのインターフェースを実装するかもしれません。これらのインタフェースに多くのメソッドがある場合、クラス内のメソッドの総数は非常に大きくなります。
たとえば、2つのインタフェースとそれを実装するクラスがあるとします。
interface Printable {
public function print();
//other interface methods...
}
interface Cacheable {
//interface methods
}
class Article implements Cachable, Printable {
//here we must implement all the interface methods
public function print(){ {
/* code to print the article */
}
}
Article
クラス内のすべてのインタフェースメソッドを実装する代わりに、別のTraitを使用してこれらのインタフェースを実装し、クラスを小さく保ち、インタフェース実装のコードをクラスから分離することができます。
たとえば、 Printable
インターフェイスを実装するために、この特性を作成することができます:
trait PrintableArticle {
//implements here the interface methods
public function print() {
/* code to print the article */
}
}
クラスにその特性を使用させる:
class Article implements Cachable, Printable {
use PrintableArticle;
use CacheableArticle;
}
主なメリットは、私たちのインターフェイス実装方法がクラスの残りの部分から分離され、 その特定の種類のオブジェクトのインターフェイスを実装する唯一の責任を持つ特性に格納されることです。
特性を使ったシングルトンの実装
免責事項 : この例では、シングルトンの使用を主張していません。シングルトンは多くの注意を払って使用する必要があります。
PHPでは、シングルトンを実装するための標準的な方法がかなりあります。
public class Singleton {
private $instance;
private function __construct() { };
public function getInstance() {
if (!self::$instance) {
// new self() is 'basically' equivalent to new Singleton()
self::$instance = new self();
}
return self::$instance;
}
// Prevent cloning of the instance
protected function __clone() { }
// Prevent serialization of the instance
protected function __sleep() { }
// Prevent deserialization of the instance
protected function __wakeup() { }
}
コードの重複を防ぐために、この振る舞いを特性に抽出することは良い考えです。
trait SingletonTrait {
private $instance;
protected function __construct() { };
public function getInstance() {
if (!self::$instance) {
// new self() will refer to the class that uses the trait
self::$instance = new self();
}
return self::$instance;
}
protected function __clone() { }
protected function __sleep() { }
protected function __wakeup() { }
}
今シングルトンとして機能したいクラスは、単にその特性を使うことができます:
class MyClass {
use SingletonTrait;
}
// Error! Constructor is not publicly accessible
$myClass = new MyClass();
$myClass = MyClass::getInstance();
// All calls below will fail due to method visibility
$myClassCopy = clone $myClass; // Error!
$serializedMyClass = serialize($myClass); // Error!
$myClass = deserialize($serializedMyclass); // Error!
シングルトンをシリアライズすることは現在不可能ですが、deserializeメソッドも禁止することはまだ有効です。