Szukaj…
Cechy ułatwiające ponowne wykorzystanie kodu w poziomie
Załóżmy, że mamy interfejs do logowania:
interface Logger {
function log($message);
}
Powiedzmy teraz, że mamy dwie konkretne implementacje interfejsu Logger
: FileLogger
i 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
}
}
Teraz, jeśli zdefiniujesz inną klasę Foo
którą również chcesz wykonywać zadania rejestrowania, możesz zrobić coś takiego:
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
jest teraz także Logger
, ale jego funkcjonalność zależy od implementacji Logger
przekazanej mu przez setLogger()
. Jeśli teraz chcemy, aby klasa Bar
miała również ten mechanizm rejestrowania, musielibyśmy powielić tę logikę w klasie Bar
.
Zamiast duplikowania kodu można zdefiniować cechę:
trait LoggableTrait {
protected $logger;
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
public function log($message) {
if ($this->logger) {
$this->logger->log($message);
}
}
}
Teraz, gdy zdefiniowaliśmy logikę dla cechy, możemy użyć tej cechy, aby dodać logikę do klas Foo
i Bar
:
class Foo {
use LoggableTrait;
}
class Bar {
use LoggableTrait;
}
I na przykład możemy użyć klasy Foo
następujący sposób:
$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');
Rozwiązanie konfliktu
Próba użycia kilku cech w jednej klasie może spowodować problemy z konfliktowymi metodami. Musisz rozwiązać takie konflikty ręcznie.
Na przykład stwórzmy tę hierarchię:
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;
}
Teraz spróbujmy utworzyć następującą klasę:
class TalkingParrot extends UnMuteAnimals {
use MeowTrait, WoofTrait;
}
Tłumacz PHP zwróci błąd krytyczny:
Błąd krytyczny: powiedzmy, że metoda cechy nie została zastosowana, ponieważ na TalkingParrot występują kolizje z innymi metodami cechy
Aby rozwiązać ten konflikt, możemy to zrobić:
- użyj słowa kluczowego
insteadof
metody, aby użyć metody z jednej cechy zamiast metody z innej cechy - utwórz alias dla metody za pomocą konstrukcji takiej jak
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();
Ten kod wygeneruje następujące dane wyjściowe:
Miauczeć
Wątek
Wykorzystanie wielu cech
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();
Powyższy przykład wyświetli:
Hello World!
Zmiana widoczności metody
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; }
}
Uruchamianie tego przykładu:
(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!
Należy więc pamiętać, że w ostatnim przykładzie w MyClass2
oryginalna niezaaloryzowana metoda z trait HelloWorld
pozostaje dostępna bez trait HelloWorld
.
Co to jest cecha?
PHP pozwala tylko na pojedyncze dziedziczenie. Innymi słowy, klasa może extend
tylko jedną inną klasę. Ale co, jeśli chcesz dołączyć coś, co nie należy do klasy nadrzędnej? Przed wersją PHP 5.4 trzeba było wykazać się kreatywnością, ale w wersji 5.4 wprowadzono cechy. Cechy pozwalają w zasadzie „skopiować i wkleić” część klasy do głównej klasy
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;
}
}
Mamy więc MrEd
, który już rozszerza Horse
. Ale nie wszystkie konie Talk
, więc mamy do tego Cechę. Zauważmy, co to robi
Najpierw definiujemy naszą Cechę. Możemy go używać z automatycznym ładowaniem i przestrzeniami nazw (zobacz także Odwoływanie się do klasy lub funkcji w przestrzeni nazw ). Następnie włączamy go do naszego MrEd
klasy ze słowem kluczowym use
.
Zauważysz, że MrEd
korzysta z funkcji i zmiennych Talk
bez ich definiowania. Pamiętasz, co powiedzieliśmy o kopiowaniu i wklejaniu ? Te funkcje i zmienne są teraz zdefiniowane w klasie, tak jakby ta klasa je zdefiniowała.
Cechy są najbardziej powiązane z klasami abstrakcyjnymi, ponieważ można definiować zmienne i funkcje. Nie można także bezpośrednio utworzyć cechy (tj. new Trait()
). Cechy nie mogą zmusić klasy do niejawnego zdefiniowania funkcji takiej jak klasa abstrakcyjna lub interfejs. Cechy są tylko dla wyraźnych definicji (ponieważ możesz implement
tyle interfejsów, ile chcesz, zobacz Interfejsy ).
Kiedy powinienem użyć cechy?
Pierwszą rzeczą, którą powinieneś zrobić, rozważając Cechę, jest zadanie sobie tego ważnego pytania
Czy mogę uniknąć używania cechy poprzez restrukturyzację kodu?
Najczęściej odpowiedź brzmi: tak . Cechy to przypadki brzegowe spowodowane pojedynczym spadkiem. Pokusa nadużywania lub nadużywania Cech może być wysoka. Ale weź pod uwagę, że Cecha wprowadza inne źródło kodu, co oznacza, że istnieje kolejna warstwa złożoności. W tym przykładzie mamy do czynienia tylko z 3 klasami. Ale Cechy oznaczają, że możesz teraz radzić sobie z czymś znacznie więcej. W przypadku każdej cechy twoja klasa staje się o wiele trudniejsza do opanowania, ponieważ musisz teraz odwołać się do każdej cechy, aby dowiedzieć się, co ona definiuje (i potencjalnie gdzie nastąpiła kolizja, zobacz Rozwiązywanie konfliktów ). Najlepiej byłoby zachować jak najmniej cech w kodzie.
Cechy utrzymania czystości klas
Z czasem nasze klasy mogą wdrażać coraz więcej interfejsów. Kiedy interfejsy te mają wiele metod, całkowita liczba metod w naszej klasie stanie się bardzo duża.
Załóżmy na przykład, że mamy dwa interfejsy i klasę, która je implementuje:
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 */
}
}
Zamiast zaimplementować wszystkie metody interfejsu w klasie Article
, moglibyśmy użyć osobnych Cech do zaimplementowania tych interfejsów, zmniejszając klasę i oddzielając kod implementacji interfejsu od klasy.
Na przykład, aby zaimplementować interfejs do Printable
, możemy stworzyć tę cechę:
trait PrintableArticle {
//implements here the interface methods
public function print() {
/* code to print the article */
}
}
i spraw, aby klasa użyła cechy:
class Article implements Cachable, Printable {
use PrintableArticle;
use CacheableArticle;
}
Podstawową korzyścią byłoby to, że nasze metody implementacji interfejsu zostaną oddzielone od reszty klasy i zapisane w cechy, która ponosi wyłączną odpowiedzialność za implementację interfejsu dla tego konkretnego typu obiektu.
Implementacja Singletona przy użyciu Cech
Zastrzeżenie : W żadnym wypadku ten przykład nie zaleca użycia singletonów. Singletony należy stosować z dużą ostrożnością.
W PHP istnieje dość standardowy sposób implementacji singletona:
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() { }
}
Aby zapobiec powielaniu kodu, dobrym pomysłem jest wyodrębnienie tego zachowania jako cechy.
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() { }
}
Teraz każda klasa, która chce funkcjonować jako singleton, może po prostu użyć cechy:
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!
Mimo że nie można teraz serializować singletonu, nadal przydatne jest również wyłączenie metody deserializacji.