Zoeken…


Kenmerken om het hergebruik van horizontale codes te vergemakkelijken

Stel dat we een interface hebben voor logboekregistratie:

interface Logger {
    function log($message);
}

Stel nu dat we twee concrete implementaties van de Logger interface hebben: de FileLogger en de 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
    }
}

Als u nu een andere klasse Foo definieert die u ook wilt kunnen vastleggen, kunt u zoiets doen:

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 is nu ook een Logger , maar zijn functionaliteit is afhankelijk van de Logger implementatie die aan setLogger() doorgegeven. Als we nu willen dat klasse Bar ook dit logboekmechanisme heeft, zouden we dit stukje logica in de klasse Bar moeten dupliceren.

In plaats van de code te dupliceren, kan een eigenschap worden gedefinieerd:

trait LoggableTrait {
    protected $logger;

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

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

Nu we de logica in een eigenschap hebben gedefinieerd, kunnen we de eigenschap gebruiken om de logica toe te voegen aan de klassen Foo en Bar :

class Foo {
    use LoggableTrait;
}

class Bar {
    use LoggableTrait;
}

En we kunnen bijvoorbeeld de Foo klasse als volgt gebruiken:

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

Conflictoplossing

Als u meerdere eigenschappen in één klasse probeert te gebruiken, kan dit leiden tot problemen met conflicterende methoden. U moet dergelijke conflicten handmatig oplossen.

Laten we bijvoorbeeld deze hiërarchie maken:

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

Laten we nu proberen de volgende klasse te maken:

class TalkingParrot extends UnMuteAnimals {
    use MeowTrait, WoofTrait;
}

De php-interpreter geeft een fatale fout:

Fatale fout: de eigenschapsmethode is niet toegepast, omdat er botsingen zijn met andere eigenschapsmethoden op TalkingParrot

Om dit conflict op te lossen, kunnen we dit doen:

  • gebruik trefwoord in insteadof van om de methode uit de ene eigenschap te gebruiken in plaats van de methode uit een andere eigenschap
  • maak een alias voor de methode met een constructie zoals 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();

Deze code zal de volgende uitvoer produceren:

mauw
Inslag

Gebruik meerdere kenmerken

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

Het bovenstaande voorbeeld zal uitvoeren:

Hello World!

Zichtbaarheid van methode wijzigen

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

Dit voorbeeld uitvoeren:

(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 dus rekening mee dat in het laatste voorbeeld in MyClass2 de oorspronkelijke methode zonder aliassen van trait HelloWorld als MyClass2 toegankelijk blijft.

Wat is een eigenschap?

PHP staat slechts een enkele overerving toe. Met andere woorden, een klasse kan slechts één andere klasse extend . Maar wat als u iets moet toevoegen dat niet thuis hoort in de bovenliggende klasse? Voorafgaand aan PHP 5.4 moest je creatief worden, maar in 5.4 werden eigenschappen geïntroduceerd. Met eigenschappen kun je in principe een deel van een klas "kopiëren en plakken" in je hoofdklasse

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

Dus hier hebben we MrEd , die Horse al uitbreidt. Maar niet alle paarden Talk , dus daar hebben we een eigenschap voor. Laten we opmerken wat dit doet

Eerst definiëren we onze eigenschap. We kunnen het gebruiken met autoloading en naamruimten (zie ook Verwijzen naar een klasse of functie in een naamruimte ). Dan voegen we het in onze MrEd klas met het trefwoord use .

U zult merken dat MrEd de Talk functies en -variabelen gebruikt zonder ze te definiëren. Weet je nog wat we zeiden over kopiëren en plakken ? Deze functies en variabelen zijn nu allemaal binnen de klasse gedefinieerd, alsof deze klasse ze heeft gedefinieerd.

Kenmerken zijn het nauwst verwant aan abstracte klassen , omdat u variabelen en functies kunt definiëren. U kunt een eigenschap ook niet direct instantiëren (bijv. new Trait() ). Eigenschappen kunnen een klasse niet dwingen om impliciet een functie te definiëren zoals een abstracte klasse of een interface. Eigenschappen zijn alleen voor expliciete definities (aangezien u zoveel interfaces kunt implement als u wilt, zie Interfaces ).

Wanneer moet ik een eigenschap gebruiken?

Het eerste wat u moet doen als u een eigenschap overweegt, is uzelf deze belangrijke vraag stellen

Kan ik voorkomen dat ik een eigenschap gebruik door mijn code te herstructureren?

Vaker wel dan niet, zal het antwoord Ja zijn . Eigenschappen zijn randgevallen die worden veroorzaakt door een enkele overerving. De verleiding om eigenschappen te misbruiken of te veel te gebruiken kan hoog zijn. Maar bedenk dat een eigenschap een andere bron voor je code introduceert, wat betekent dat er een andere laag van complexiteit is. In het voorbeeld hier hebben we slechts te maken met 3 klassen. Maar met eigenschappen kun je nu veel meer te maken krijgen. Voor elke eigenschap wordt je klasse veel moeilijker om mee om te gaan, omdat je nu naar elke eigenschap moet verwijzen om erachter te komen wat het definieert (en mogelijk waar een botsing plaatsvond, zie Conflictoplossing ). In het ideale geval moet u zo weinig mogelijk eigenschappen in uw code bewaren.

Eigenschappen om klassen schoon te houden

Na verloop van tijd kunnen onze klassen steeds meer interfaces implementeren. Wanneer deze interfaces veel methoden hebben, wordt het totale aantal methoden in onze klasse erg groot.

Laten we bijvoorbeeld veronderstellen dat we twee interfaces hebben en een klasse die deze implementeert:

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

In plaats van de uitvoering van alle van de interface methoden in het Article klasse, konden we aparte Traits gebruiken om deze interfaces te implementeren, het bijhouden van de klasse kleiner en het scheiden van de code van de interface implementatie van de klas.

Om bijvoorbeeld de Printable interface te implementeren, kunnen we deze eigenschap maken:

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

en laat de klas de eigenschap gebruiken:

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

De belangrijkste voordelen zijn dat onze methoden voor interface-implementatie worden gescheiden van de rest van de klas en worden opgeslagen in een eigenschap die als enige verantwoordelijk is voor het implementeren van de interface voor dat specifieke type object.

Een Singleton implementeren met behulp van eigenschappen

Disclaimer : op geen enkele manier pleit dit voorbeeld voor het gebruik van singletons. Singletons worden met veel zorg gebruikt.

In PHP is er een vrij standaard manier om een singleton te implementeren:

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

Om codeduplicatie te voorkomen, is het een goed idee om dit gedrag in een eigenschap te extraheren.

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 elke klasse die als een singleton wil functioneren eenvoudig de eigenschap gebruiken:

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!

Hoewel het nu onmogelijk is om een singleton te serialiseren, is het nog steeds nuttig om de deserialize-methode ook niet toe te staan.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow