Suche…


Eigenschaften zur Erleichterung der horizontalen Wiederverwendung von Code

Nehmen wir an, wir haben eine Schnittstelle für die Protokollierung:

interface Logger {
    function log($message);
}

FileLogger , wir haben zwei konkrete Implementierungen der Logger Schnittstelle: den FileLogger und den 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
    }
}

Wenn Sie jetzt eine andere Klasse Foo die auch Protokollierungsaufgaben ausführen soll, können Sie Folgendes tun:

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 ist jetzt auch ein Logger , aber seine Funktionalität hängt von der über setLogger() an ihn setLogger() Logger Implementierung ab. Wenn wir jetzt Klasse wollen Bar auch diesen Logging - Mechanismus haben, würden wir dieses Stück Logik in der duplizieren Bar - Klasse.

Anstatt den Code zu duplizieren, kann eine Eigenschaft definiert werden:

trait LoggableTrait {
    protected $logger;

    public function setLogger(Logger $logger) {
        $this->logger = $logger;
    }

    public function log($message) {
        if ($this->logger) {
            $this->logger->log($message);
        }
    }
}

Nachdem wir nun die Logik in einem Merkmal definiert haben, können wir das Merkmal verwenden, um die Logik zu den Klassen Foo und Bar hinzuzufügen:

class Foo {
    use LoggableTrait;
}

class Bar {
    use LoggableTrait;
}

Und zum Beispiel können wir die Foo Klasse folgendermaßen verwenden:

$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'); 

Konfliktlösung

Wenn Sie versuchen, mehrere Merkmale in einer Klasse zu verwenden, kann dies zu Problemen führen, die zu widersprüchlichen Methoden führen. Sie müssen solche Konflikte manuell lösen.

Lassen Sie uns zum Beispiel diese Hierarchie erstellen:

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

Versuchen wir nun die folgende Klasse zu erstellen:

class TalkingParrot extends UnMuteAnimals {
    use MeowTrait, WoofTrait;
}

Der PHP-Interpreter gibt einen schwerwiegenden Fehler zurück:

Schwerwiegender Fehler: Die Merkmalmethode say wurde nicht angewendet, da bei TalkingParrot Kollisionen mit anderen Merkmalmethoden auftreten

Um diesen Konflikt zu lösen, können wir Folgendes tun:

  • Verwenden insteadof stattdessen ein Schlüsselwort, um die Methode eines Merkmals anstelle der Methode eines anderen Merkmals zu verwenden
  • Erstellen Sie einen Alias ​​für die Methode mit einem Konstrukt wie 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();

Dieser Code erzeugt die folgende Ausgabe:

Miau
Schuss

Verwendung mehrerer Merkmale

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

Das obige Beispiel gibt Folgendes aus:

Hello World!

Sichtbarkeit der Methode ändern

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

Dieses Beispiel ausführen:

(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 dass im letzten Beispiel in MyClass2 die ursprüngliche unaliasierte Methode aus dem trait HelloWorld HelloWorld unverändert bleibt.

Was ist eine Eigenschaft?

PHP erlaubt nur die Einzelvererbung. Mit anderen Worten kann eine Klasse nur extend eine andere Klasse. Was aber, wenn Sie etwas hinzufügen müssen, das nicht in die übergeordnete Klasse gehört? Vor PHP 5.4 müsste man kreativ werden, aber in 5.4 wurden Traits eingeführt. Mit Eigenschaften können Sie einen Teil einer Klasse grundsätzlich in Ihre Hauptklasse "kopieren und einfügen"

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

Hier haben wir also MrEd , das Horse bereits erweitert. Aber nicht alle Pferde Talk , also haben wir dafür ein Merkmal. Lassen Sie uns festhalten, was dies tut

Zuerst definieren wir unser Merkmal. Wir können es mit Autoloading und Namespaces verwenden (siehe auch Klasse oder Funktion in einem Namespace referenzieren ). Dann MrEd wir es mit dem Schlüsselwort use in unsere MrEd Klasse ein.

Sie werden bemerken , dass MrEd nimmt die zur Verwendung von Talk ohne zu definieren , sie Funktionen und Variablen. Erinnern Sie sich daran, was wir über das Kopieren und Einfügen gesagt haben? Diese Funktionen und Variablen sind jetzt alle innerhalb der Klasse definiert, als hätten sie diese Klasse definiert.

Merkmale sind am engsten mit abstrakten Klassen verbunden , da Sie Variablen und Funktionen definieren können. Sie können eine Eigenschaft auch nicht direkt instanziieren (dh eine new Trait() ). Traits können eine Klasse nicht dazu zwingen, eine Funktion implizit zu definieren, wie dies eine Abstract-Klasse oder ein Interface kann. Traits dienen nur für explizite Definitionen (da Sie beliebig viele Interfaces implement können, siehe Interfaces ).

Wann sollte ich ein Merkmal verwenden?

Wenn Sie ein Merkmal in Betracht ziehen, sollten Sie sich zuerst diese wichtige Frage stellen

Kann ich die Verwendung eines Traits vermeiden, indem ich meinen Code umstrukturiere?

Meistens wird die Antwort ja sein . Eigenschaften sind Randfälle, die durch einfache Vererbung verursacht werden. Die Versuchung, Merkmale zu missbrauchen oder zu missbrauchen, kann hoch sein. Bedenken Sie jedoch, dass ein Trait eine andere Quelle für Ihren Code einführt, was bedeutet, dass es eine andere Komplexitätsebene gibt. In diesem Beispiel handelt es sich nur um 3 Klassen. Aber Eigenschaften bedeuten, dass Sie jetzt mit weit mehr umgehen können. Für jedes Merkmal ist es schwieriger, mit Ihrer Klasse umzugehen, da Sie nun auf jedes Merkmal verweisen müssen, um herauszufinden, was es definiert (und möglicherweise den Ort einer Kollision, siehe Konfliktlösung ). Idealerweise sollten Sie so wenig Merkmale wie möglich in Ihrem Code behalten.

Eigenschaften, um den Unterricht sauber zu halten

Im Laufe der Zeit implementieren unsere Klassen möglicherweise immer mehr Schnittstellen. Wenn diese Schnittstellen über viele Methoden verfügen, wird die Gesamtzahl der Methoden in unserer Klasse sehr groß.

Nehmen wir zum Beispiel an, wir haben zwei Schnittstellen und eine Klasse, die sie implementiert:

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

Anstatt alle Schnittstellenmethoden innerhalb der Article Klasse zu implementieren, könnten wir separate Schnittstellen verwenden, um diese Schnittstellen zu implementieren, die Klasse kleiner zu halten und den Code der Schnittstellenimplementierung von der Klasse zu trennen.

Printable beispielsweise die Printable Schnittstelle zu implementieren, können Sie dieses Merkmal erstellen:

trait PrintableArticle {
    //implements here the interface methods
    public function print() {
        /* code to print the article */ 
    }
}

und die Klasse das Merkmal verwenden:

class Article implements Cachable, Printable {
    use PrintableArticle;
    use CacheableArticle; 
} 

Der Hauptvorteil wäre, dass unsere Schnittstellenimplementierungsmethoden vom Rest der Klasse getrennt und in einem Merkmal gespeichert werden, das die alleinige Verantwortung für die Implementierung der Schnittstelle für diesen bestimmten Objekttyp trägt.

Ein Singleton mit Traits implementieren

Haftungsausschluss : In diesem Beispiel wird keinesfalls die Verwendung von Singletons befürwortet. Singletons sind mit großer Sorgfalt zu verwenden.

In PHP gibt es eine Standardmethode, um ein Singleton zu implementieren:

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() { }
}

Um das Duplizieren von Code zu verhindern, empfiehlt es sich, dieses Verhalten in eine Eigenschaft zu extrahieren.

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() { }
}

Nun kann jede Klasse, die als Einzelspieler fungieren möchte, einfach die Eigenschaft verwenden:

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!

Obwohl es jetzt unmöglich ist, ein Singleton zu serialisieren, ist es dennoch nützlich, die Deserialisierungsmethode nicht zuzulassen.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow