Ricerca…


Tratti per facilitare il riutilizzo del codice orizzontale

Supponiamo di avere un'interfaccia per la registrazione:

interface Logger {
    function log($message);
}

Ora diciamo che abbiamo due concrete implementazioni dell'interfaccia di Logger : FileLogger e 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
    }
}

Ora se si definisce qualche altra classe Foo che si desidera anche essere in grado di eseguire attività di logging, si potrebbe fare qualcosa del genere:

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 ora è anche un Logger , ma la sua funzionalità dipende dall'implementazione di Logger passata tramite setLogger() . Se ora vogliamo che anche la Bar classe abbia questo meccanismo di registrazione, dovremmo duplicare questa parte di logica nella classe Bar .

Invece di duplicare il codice, è possibile definire un tratto:

trait LoggableTrait {
    protected $logger;

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

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

Ora che abbiamo definito la logica in un tratto, possiamo usare il tratto per aggiungere la logica alle classi Foo e Bar :

class Foo {
    use LoggableTrait;
}

class Bar {
    use LoggableTrait;
}

E, per esempio, possiamo usare la classe Foo questo modo:

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

Risoluzione del conflitto

Cercare di utilizzare diversi tratti in una classe potrebbe causare problemi con metodi in conflitto. È necessario risolvere manualmente tali conflitti.

Ad esempio, creiamo questa gerarchia:

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

Ora proviamo a creare la seguente classe:

class TalkingParrot extends UnMuteAnimals {
    use MeowTrait, WoofTrait;
}

L'interprete PHP restituirà un errore fatale:

Errore irreversibile: il metodo dei tratti dice che non è stato applicato, perché su TalkingParrot vi sono collisioni con altri metodi di tratto

Per risolvere questo conflitto, potremmo fare questo:

  • usa keyword insteadof per usare il metodo da una caratteristica invece di un metodo da un'altra caratteristica
  • crea un alias per il metodo con un costrutto come 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();

Questo codice produrrà il seguente risultato:

Miao
Trama

Uso di più tratti

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

L'esempio sopra uscirà:

Hello World!

Modifica della visibilità del metodo

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

Esecuzione di questo esempio:

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

Quindi, MyClass2 presente che nell'ultimo esempio in MyClass2 il metodo originale senza alias di trait HelloWorld rimane accessibile così com'è.

Cos'è un tratto?

PHP consente solo l'ereditarietà singola. In altre parole, una classe può extend solo un'altra classe. Ma cosa succede se è necessario includere qualcosa che non appartiene alla classe genitore? Prima di PHP 5.4 dovevi diventare creativo, ma nel 5.4 sono stati introdotti i tratti. I tratti ti consentono di "copiare e incollare" sostanzialmente una porzione di una classe nella tua classe principale

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

Quindi qui abbiamo MrEd , che sta già estendendo Horse . Ma non tutti i cavalli Talk , quindi abbiamo un tratto per questo. Prendiamo nota di ciò che sta facendo

Per prima cosa, definiamo il nostro tratto. Possiamo usarlo con autoloading e Namespaces (vedi anche Riferimento a una classe o funzione in un namespace ). Quindi lo includiamo nella nostra classe MrEd con la parola chiave use .

Noterai che MrEd impiega le funzioni e le variabili di Talk senza definirle. Ricorda cosa abbiamo detto di copia e incolla ? Queste funzioni e variabili sono tutte definite all'interno della classe ora, come se questa classe le avesse definite.

I tratti sono strettamente correlati alle classi astratte in quanto è possibile definire variabili e funzioni. Inoltre, non è possibile creare direttamente un'istanza di un tratto (vale a dire un new Trait() ). I tratti non possono forzare una classe a definire implicitamente una funzione come una classe astratta o una lattina di interfaccia. I tratti sono solo per definizioni esplicite (dato che puoi implement tutte le interfacce che vuoi, vedi Interfacce ).

Quando dovrei usare un tratto?

La prima cosa che dovresti fare, quando consideri un Tratto, è di porsi questa importante domanda

Posso evitare di usare un tratto ristrutturando il mio codice?

Più spesso, la risposta sarà . I tratti sono casi limite causati dall'eredità singola. La tentazione di utilizzare in modo improprio o eccessivo i Tratti può essere alta. Ma considera che un tratto introduce un'altra fonte per il tuo codice, il che significa che c'è un altro livello di complessità. Nell'esempio qui, ci occupiamo solo di 3 classi. Ma i tratti significano che ora puoi avere a che fare molto di più. Per ogni Tratto, la tua classe diventa molto più difficile da gestire, dal momento che ora devi fare riferimento a ciascun Tratto per scoprire cosa definisce (e potenzialmente dove è avvenuta una collisione, vedi Risoluzione dei conflitti ). Idealmente, dovresti mantenere il minor numero possibile di caratteri nel tuo codice.

Tratti per mantenere pulite le classi

Nel tempo, le nostre classi potrebbero implementare sempre più interfacce. Quando queste interfacce hanno molti metodi, il numero totale di metodi nella nostra classe diventerà molto grande.

Ad esempio, supponiamo di avere due interfacce e una classe che le implementa:

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

Invece di implementare tutti i metodi dell'interfaccia all'interno della classe Article , potremmo utilizzare tratti separati per implementare queste interfacce, mantenendo la classe più piccola e separando il codice dell'implementazione dell'interfaccia dalla classe.

Dall'esempio, per implementare l'interfaccia Printable , potremmo creare questo tratto:

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

e fai in modo che la classe usi il tratto:

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

Il vantaggio principale sarebbe che i nostri metodi di implementazione dell'interfaccia saranno separati dal resto della classe e memorizzati in un tratto che ha la sola responsabilità di implementare l'interfaccia per quel particolare tipo di oggetto.

Implementare un Singleton usando i Tratti

Disclaimer : in nessun modo questo esempio sostiene l'uso di singleton. I single devono essere usati con molta cura.

In PHP esiste un modo abbastanza standard per implementare un 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() { }
}

Per evitare la duplicazione del codice, è una buona idea estrarre questo comportamento in un tratto.

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

Ora qualsiasi classe che vuole funzionare come un singleton può semplicemente usare il tratto:

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!

Anche se ora è impossibile serializzare un singleton, è comunque utile disabilitare anche il metodo deserialize.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow