Quantcast
Channel: devhelp.pl » traits
Viewing all articles
Browse latest Browse all 2

Potencjał Traits

$
0
0

W jednym z poprzednich artykułów mówiliśmy o nowym mechaniźmie języka php, którym są traits. Wprowadzenie ich wywołało dużo kontrowersji. Ten artykuł ma na celu próbę pokazania, że traits faktycznie mogą okazać się przydatne.

Szukając materiałów na prezentacje o traits, natknąłem się w sieci na ciekawy przykład zastosowania. Chodziło o trait, który umożliwiał zwrócenie obiektu w postaci JSON’a. Przykład widoczny poniżej jest dosyć prosty i można mieć do niego zastrzeżenia, ale nie to przykuło moją uwagę.

trait JSONized 
{
    public function toJson() {
        $properties = get_object_vars($this);
        return json_encode($properties);
    }
}

 
Oryginalny artykuł dostępny jest tutaj

„We need to go deeper”

To co widzieliśmy przy okazji JSONized to mapowanie obiektu na określoną strukturę. W tym przypadku był to JSON, ale przecież może być to cokolwiek innego… co powiecie na tabele w bazie danych ? A może tak implementacja Active Record bez narzuconej hierarchii dziedziczenia !?

trait ActiveRecord
{
    public function save() { /*...*/ }
 
    protected function update() { /*…*/ }
 
    protected function insert() { /*…*/ }
 
    public function delete() { /*...*/ }
}
 
class Article extends Whatever
{
    use ActiveRecord;
    ...
}

 
Moim zdaniem ma to wielki sens i świetnie wpasowuje się w to co chcemy przedstawić, w ten wycinek rzeczywistości, który chcemy zinterpretować za pomocą kodu. Wróćmy do Active Record i powyższego przypadku, ale bez traits. Czym jest Artykuł ? Czy na prawdę jest rekordem ? Albo czym jest inny obiekt, który chcemy zmapować, powiedzmy Użytkownik – czy on też dziedziczy po rekordzie ? Otóż nie ! To jest z góry bez sensu logicznie, ma nijak do rzeczywistości. On jest rekordem, bo inaczej nie zaimplementujemy Active Record, ale to co chcemy na prawdę zrobić to sprawić, żeby Artykuł, Użytkownik i inne mapowane obiekty zachowywały się jak rekord. Takie zastosowanie traits pozwala na uniknięcie głównego grzechu Active Record, czyli narzuconej hierarchii dziedziczenia.

Od zera do traits

Prześledźmy od początku próbę napisania w miarę sprawnego i dobrze zaprojektowanego logowania do jednej z klas aplikacji. Spójrzmy na poniższy kod:

class IAmUsingLogging
{
    public function doSomething() {
        $this->log('I am about to do something');
        // I am doing something
        $this->log(”I've done it!”);
    }
 
    private function log($message) {
        // I am doin logging
    }
}

 
Kod zadziała, ale czy jest to dobry projekt ? Co jeżeli inna klasa też chciała by logować swoje akcje ? Mamy duplikować kod ? Logowanie jest na tyle odrębnym zestawem operacji, że zasługuje na własną klasę, do której należałoby oddelegować wykonywanie logowania. Od razu uspokajam – jeszcze nie wprowadzmy traits, czyli absolutnie nie robimy czegoś takiego jak poniżej.

trait Logging
{
    private function log($message) {
        // I am doin logging
    }
}
 
class IAmUsingLogging
{
    use Logging;
 
    public function doSomething() {
        $this->log('I am about to do something');
        // I am doing something
        $this->log(”I've done it!”);
    }
}
 
class IAmUsingLoggingToo
{
    use Logging;
 
    public function doSomething() {
        $this->log('I am about to do something');
        // I am doing something
        $this->log(”I've done it!);
    }
}

 
To jest przykład na coś co może się stać kiedy ludzie którzy nie mają za dużo pojęcia o wzrocach projektowych, o klasach usługowych, kompozycji, zaczną używać traits.

Traits to potężna broń. Pozwala zepsuć projekt aplikacji, a przy tym spowodować, że nawet linijka kodu się nie powtórzy

To co powinniśmy zrobić to stworzyć osobną klasę loggera i do niej oddelegować metody logujące.

interface LoggerInterface
{
    public function log($message);
}
 
class Logger implements LoggerInterface
{
    public function log($message) {
        // I am doin logging
    }
}
 
class IAmUsingLogging implements LoggerInterface
{
    private $logger;
 
    public function setLogger(LoggerInterface $logger) {
        $this->logger = $logger;
    }
 
    private function log($message) {
        $this->logger->log($message);
    }
 
    public function doSomething() {
        $this->log('I am about do do something');
        // I am doing something
        $this->log(”I've done it!”);
    }	
}

 
Nasz wycinek aplikacji zaczyna mieć ręcę i nogi. Logger robi ciężką robotę, klasa która logowania potrzebuje oddelegowuje do niego zadania. Dla porządku, zarówno Logger jak i nasza klasa implementują ten sam interfejs. I nagle do gry wchodzi kolejna klasa która chce używać logowania.

class IAmUsingLoggingToo implements LoggerInterface
{
    private $logger;	
 
    public function setLogger(LoggerInterface $logger) {
        $this->logger = $logger;
    }
 
    private function log($message) {
        $this->logger->log($message);
    }
 
    public function doSomething() {
        $this->log('I am about do do something');
        // I am doing something
        $this->log(”I've done it!”);
    }	
}

 
Sytuacja nie jest krytyczna. Ciężką robotę nadal wykonuje Logger. Widzimy jednak, że musimy powtórzyć 3 elementy. Ich implementacja jest banalna, jednak zawsze są to 3 elementy na każdą klasę wymagającą logowania. Sądzę, że do takich przypadków – pole przechowujące klasę usługową, setter, metody wynikające z interfejsu klasy usługowej – traits nadają się znakomicie. Spójrzmy:

trait Logging
{
    private $logger;
 
    public function setLogger(LoggerInterface $logger)  {
        $this->logger = $logger;
    }
 
    public function log($message)  {
        $this->logger->log($message);
    }
}
 
class IAmUsingLogging implements LoggerInterface
{
    use Logging;
 
    public function doSomething() {
        $this->log('I am about do do something');
        //... I am doing something
        $this->log(”I've done it!”);
    }
}

 

Traits jako Doctrine Behaviors

Traits ma za zadanie określać „zachowanie” klasy. Idąc tym krokiem rozumowania możemy skojarzyć to ze znanymi z Doctrine 1.x behaviorami. W rzeczywistości, sposób „wpinania” behaviorów do klasy poprzez słowo kluczowe actAs bardzo przypomina use znane z traits. W Doctrine 2 znikły znane z Doctrine 1 behaviory i, sądząc po notce na stronie projektu (tutaj), nic nie wskazuje na to że się pojawią. Firma Knp Labs, której chyba głównym obszarem zainteresowań jest Symfony 2 wypuściła jednak ostatnio bardzo ciekawą paczkę z behaviorami pod Doctrine 2, które są oparte właśnie na traits ! Przypominam tylko, że devhelp również sygnalizował podobieństwa pomiędzy tymi mechanizmami w poprzednim artykule :) . Link do artykułu na Knp Labs znajduje się tutaj, polecam również zajrzeć na ich konto na githubie z kodem źródłowym.

Traits należy używać bardzo ostrożnie. Na 90% nie mają sensu ich przy prawidłowo zaprojektowanej aplikacji, ponieważ większość problemów da się rozwiązać przez wzorce projektowe.
Wg mnie traits chwieje podstawami systemu obiektowego. Co jeśli wprowadzenie traits niejako wytyka braki w podejściu do obiektowości, a niektóre wzorce projektowe istnieją w swojej aktualnej postaci tylko dlatego, żeby te braki zakryć (Active Record) ? „Socjalizm bohatersko walczy z problemami, które sam tworzy” – może część wzorców to właśnie oręż w socjalistycznym modelu obiektów :)

 


Viewing all articles
Browse latest Browse all 2

Trending Articles


TRX Antek AVT - 2310 ver 2,0


Автовишка HAULOTTE HA 16 SPX


POTANIACZ


Zrób Sam - rocznik 1985 [PDF] [PL]


Maxgear opinie


BMW E61 2.5d błąd 43E2 - klapa gasząca a DPF


Eveline ➤ Matowe pomadki Velvet Matt Lipstick 500, 506, 5007


Auta / Cars (2006) PLDUB.BRRip.480p.XviD.AC3-LTN / DUBBING PL


Peugeot 508 problem z elektroniką


AŚ Jelenia Góra