Sök…


Egenskaper för att underlätta återanvändning av horisontell kod

Låt oss säga att vi har ett gränssnitt för loggning:

interface Logger {
    function log($message);
}

Säg nu att vi har två konkreta implementationer av Logger gränssnittet: FileLogger och 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
    }
}

Om du nu definierar någon annan Foo klass som du också vill kunna utföra loggningsuppgifter kan du göra något liknande:

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 är nu också en Logger , men dess funktionalitet beror på Logger implementeringen som skickas till den via setLogger() . Om vi nu vill att Bar också ska ha den här loggningsmekanismen, skulle vi behöva kopiera detta logikstycke i Bar .

Istället för att kopiera koden kan ett drag definieras:

trait LoggableTrait {
    protected $logger;

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

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

Nu när vi har definierat logiken i ett drag kan vi använda draget för att lägga till logiken i Foo och Bar klasserna:

class Foo {
    use LoggableTrait;
}

class Bar {
    use LoggableTrait;
}

Och till exempel kan vi använda Foo klassen så här:

$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ösning

Att försöka använda flera egenskaper i en klass kan resultera i problem med motstridiga metoder. Du måste lösa sådana konflikter manuellt.

Låt oss till exempel skapa denna hierarki:

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

Låt oss försöka skapa följande klass:

class TalkingParrot extends UnMuteAnimals {
    use MeowTrait, WoofTrait;
}

PHP-tolken kommer att returnera ett dödligt fel:

Dödligt fel: Säkerhetsmetod har inte tillämpats, eftersom det finns kollisioner med andra dragmetoder på TalkingParrot

För att lösa konflikten kan vi göra detta:

  • använd nyckelord insteadof att använda metoden från ett drag i stället för metod från ett annat drag
  • skapa ett alias för metoden med en konstruktion som 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();

Denna kod kommer att producera följande utgång:

mjau
Väft

Användning av flera egenskaper

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

Exemplet ovan kommer att matas ut:

Hello World!

Ändra metodens synlighet

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

Kör detta exempel:

(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!

Så var medveten om att i det sista exemplet i MyClass2 den ursprungliga o-aliasmetoden från trait HelloWorld tillgänglig som den är.

Vad är en egenskap?

PHP tillåter endast enstaka arv. Med andra ord kan en klass bara extend en annan klass. Men vad händer om du behöver inkludera något som inte hör till förälderklassen? Innan PHP 5.4 skulle du behöva bli kreativ, men i 5.4 introducerades egenskaper. Med egenskaper kan du i princip "kopiera och klistra in" en del av en klass i din huvudklass

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

Så här har vi MrEd , som redan förlänger Horse . Men inte alla hästar Talk , så vi har ett drag för det. Låt oss notera vad det här gör

Först definierar vi vårt drag. Vi kan använda den med autoloadning och namnutrymmen (se även Hänvisning till en klass eller funktion i ett namnområde ). Då kan vi inkludera den i vår MrEd klass med sökordet use .

Du kommer att notera att MrEd använder sig av Talk funktionerna och variablerna utan att definiera dem. Kommer du ihåg vad vi sa om kopiera och klistra in ? Dessa funktioner och variabler definieras alla inom klassen nu, som om den här klassen hade definierat dem.

Egenskaper är närmast relaterade till abstrakt klasser genom att du kan definiera variabler och funktioner. Du kan inte heller initiera en egenskap direkt (dvs. new Trait() ). Funktioner kan inte tvinga en klass att implicit definiera en funktion som en abstrakt klass eller ett gränssnitt kan. Egenskaper är endast för uttryckliga definitioner (eftersom du kan implement så många gränssnitt som du vill, se gränssnitt ).

När ska jag använda en egenskap?

Det första du bör göra när du överväger en egenskap är att ställa dig själv denna viktiga fråga

Kan jag undvika att använda ett drag genom att omstrukturera min kod?

Oftare än inte kommer svaret att vara Ja . Egenskaper är kantfall orsakade av enstaka arv. Frestelsen att missbruka eller överanvända egenskaper kan vara hög. Men tänk på att ett drag introducerar en annan källa för din kod, vilket innebär att det finns ett annat lager av komplexitet. I exemplet här handlar det bara om tre klasser. Men drag betyder att du nu kan hantera mycket mer än så. För varje drag blir din klass så mycket svårare att hantera, eftersom du nu måste gå till varje drag för att ta reda på vad den definierar (och eventuellt var en kollision inträffade, se konfliktlösning ). Helst bör du hålla så få egenskaper i din kod som möjligt.

Egenskaper för att hålla klasserna rena

Med tiden kan våra klasser implementera fler och fler gränssnitt. När dessa gränssnitt har många metoder kommer det totala antalet metoder i vår klass att bli mycket stort.

Låt oss till exempel anta att vi har två gränssnitt och en klass som implementerar dem:

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

Istället för att implementera alla gränssnittsmetoder i Article , kan vi använda separata egenskaper för att implementera dessa gränssnitt, hålla klassen mindre och separera koden för gränssnittsimplementeringen från klassen.

Till exempel för att implementera det Printable gränssnittet kan vi skapa detta drag:

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

och få klassen att använda egenskaperna:

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

De främsta fördelarna skulle vara att våra gränssnittsimplementeringsmetoder kommer att separeras från resten av klassen och lagras i en egenskap som har det enda ansvaret att implementera gränssnittet för den specifika typen av objekt.

Implementera en Singleton med drag

Friskrivningsklausul : Detta exempel förespråkar inte på något sätt användningen av singletoner. Singletoner ska användas med stor omsorg.

I PHP finns det ett ganska vanligt sätt att implementera en singleton:

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

För att förhindra koddubbling är det en bra idé att extrahera detta beteende till ett drag.

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

Nu kan varje klass som vill fungera som singleton helt enkelt använda egenskaperna:

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!

Även om det nu är omöjligt att serialisera en singleton, är det fortfarande användbart att inte tillåta deserialiseringsmetoden.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow