Zastosowanie wybranych wzorców projektowych w praktyce na przykładzie działającej aplikacji oraz wybranych frameworków PHP
Size: 187.34 KB
Language: none
Added: Mar 17, 2020
Slides: 71 pages
Slide Content
Wzorce projektowe w praktyce
Marek Keżel Kierownik projektu autorskiego systemu ERP do zarządzania produkcją mebli tapicerowanych Programista PHP, JS, Python Linux, CCNA R&S, CCNA Sec O mnie
Agenda Czym jest wzorzec projektowy ? Singleton - nikt go nie lubi Simple Factory - tworzyć, prosto Adapter - dopasowywać Facade - upraszczać Observer - powiadamiać Dependency Injection - wstrzykiwać zależności
CZYM JEST wzorZEC projektowY ?
In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. Rather, it is a description or template for how to solve a problem that can be used in many different situations. Wikipedia
CZYM JEST WZORZEC PROJEKTOWY Ogólnym rozwiązaniem często spotykanego problemu projektowego. Szablonem rozwiązania problemu Sformalizowanym elementem najlepszych praktyk programistycznych
CZYM NIE JEST WZORZEC PROJEKTOWY Gotową implementacją rozwiązania problemu którą możemy skopiować do naszego projektu Szablonem rozwiązania w konkretnym języku*
UWAGA! Stosowanie wzorców projektowych “na siłę” jest anty wzorcem. Jeżeli jest problem z dobraniem wzorca może lepiej odpuścić sobie stosowanie jakiekolwiek w tym kontekście. Kod bez wzorców projektowych będzie działał*
Wzorce projektowe wg bandy czworga - CZYNNOŚCIOWE Chain of Responsibility Command Interpreter Iterator Mediator Memento Observer State Strategy Template Method Visitor
SINGLETON
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one "single" instance. This is useful when exactly one object is needed to coordinate actions across the system. Wikipedia
W czym MOŻE pomóC? Jedna instancja klasy podczas działania aplikacji Globalny dostęp do w/w instancji* Ulepszona wersja zmiennej globalnej
<?php class Logger { protected static $instance ; private function __construct () {} private function __clone () {} private function __wakeup () {} public static function getInstance () { if ( is_null ( static :: $instance)) { static :: $instance = new static; } return static :: $instance ; } public function info ( $message , $category ) { //zapisać do bazy danych } } PrzykładowA IMPLEMENTACJA
<?php class Trello { protected static $instance ; private function __construct () {} private function __clone () {} private function __wakeup () {} public static function getInstance () { if ( is_null ( static :: $instance)) { static :: $instance = new static; } return static :: $instance ; } public function dodajKarte ( $lista , $parametry ) { //połączyć się z trello i dodać kartę do listy } } PrzykładowA IMPLEMENTACJA
Jakie ma wady ? ANTYWZORZEC ? Silne wiązania między kodem wykorzystującym singletona a nim samym poprzez dostęp globalny (wiązanie z kontekstem) Ukrywanie zależności Naruszenie zasad SOLID: Dependency Inversion Principle, Single Responsibility Principle Testowanie, jak podmienić singletona na potrzeby testów. Testy mogą zacząć być zależne od siebie. Jeżeli jeden test zmieni stan obiektu drugi może pracować na zmienionym już stanie a nie na początkowym.
<?php class Zgloszenie extends ActiveRecord { public function create () { if ($this -> save ()) { Logger:: getInstance () -> info ( 'Dodano zlgoszenie' , __METHOD__) ; $this -> wyslijPowiadomienie () ; return true; } return false; } public function wyslijPowiadomienie () { Trello:: getInstance () -> dodajKarte ( 'zgloszenia' , [ 'zrodlo' => $this -> zlodlo , 'temat' => $this -> temat , 'tresc' => $this -> tresc , ] ) ; } } PrzykładOWE UŻYCIE
Co zrobić jeżeli POTRZEBNA JEST tylko jednA INSTANCJA KLASY w aplikacji ? Czy na pewno potrzebna jest tylko jedna instancja? Użyć Dependency Injection Użyć Service Locator* Użyć świadomie singletona z pełną świadomością jego konsekwencji
<?php class Zgloszenie extends ActiveRecord { protected $logger ; protected $trello ; public function __construct (LoggerInterface $logger , TrelloInterface $trello ) { $this -> logger = $logger ; $this -> trello = $trello ; } public function create () { if ($this -> save ()) { $this -> logger -> info ( 'Dodano zlgoszenie' , __METHOD__) ; $this -> wyslijPowiadomienie () ; return true; } return false; } public function wyslijPowiadomienie () { $this -> trello -> dodajKarte ( 'zgloszenia' , [ 'zrodlo' => $this -> zlodlo , 'temat' => $this -> temat , 'tresc' => $this -> tresc , ] ) ; } } PRZYKŁADOWE UŻYCIE - ZAMIAST SINGLETONA - DI
<?php $container = new \yii\di\Container ; $container -> setSingleton ( 'LoggerInterface' , 'Logger' ) ; $container -> setSingleton ( 'TrelloInterface' , 'Trello' ) ; $zgloszenie = $container -> get ( 'Zgloszenie' ) ; PRZYKŁADOWE UŻYCIE - ZAMIAST SINGLETONA - DI
In object-oriented programming (OOP), a factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class from some method call, which is assumed to be "new". Wikipedia
W czym MOŻE pomóC? Tworzenie obiektów z rodziny klas ze wspólnym interfejsem na podstawie parametru Enkapsulacja procesu tworzenia obiektów - nie pokazujemy logiki procesu tworzenia Uniknięcie warunkowych procesów tworzenia obiektów Centralna lokalizacja tworzenia obiektów - łatwo zareagujemy np na zmiany wymaganych parametrów konstruktora DRY
<?php class ArtykulConverterFactory { public function createConverter ( int $typ ): ArtykulConverterInterface { switch ($typ) { case ZamowieniePozycje:: TYP_LOZKO : return new LozkoConverter () ; case ZamowieniePozycje:: TYP_SZAFKA : return new SzafkaConverter () ; case ZamowieniePozycje:: TYP_PUFA : return new PufaConverter () ; default : throw new IndeksException ( "Błędny typ artykułu" ) ; } } } Przykładowa IMPLEMENTACJA
class IndeksFacade { protected $typ ; protected $konwerterFactory ; protected $service ; public function __construct ( int $typ , ArtykulConverterFactoryInterface $konwerterFactory , IndeksServiceInterface $service ) { $this -> typ = $typ ; $this -> konwerterFactory = $konwerterFactory ; $this -> service = $service ; } public function utworzZKreatora ( $konfiguracjaZKreatora ): Indeks { $konfiguracja = $this -> getKonwerter () -> zKreatora ($konfiguracjaZKreatora) ; return $this -> service -> utworz ($konfiguracja) ; } public function utworzZPozycji (AbstractPozycja $pozycja ): Indeks { $konfiguracja = $this -> getKonwerter () -> zPozycji ($pozycja) ; return $this -> service -> utworz ($konfiguracja) ; } public function getKonwerter (): ArtykulConverterInterface { return $this -> konwerterFactory -> createConverter ($this -> typ) ; } } PrzykładowE UŻYCIE
Jakie ma wady ? Większa złożoność kodu Większa ilość klas Wersja simple factory łamię zasadę Open Close Principle (SOLID).
<?php class ArtykulConverterFactory { public function createConverter ( int $typ ): ArtykulConverterInterface { $mapa = Yii:: $app -> params [ 'mapaArtykulow' ] ; if ( !array_key_exists ($typ , $mapa)) { throw new IndeksException ( "Błędny typ artykułu" ) ; } $converterFQN = $mapa [ $typ ] . 'Converter' ; return new $converterFQN ; } } Przykładowa IMPLEMENTACJA
ADAPTER
In software engineering, the adapter pattern is a software design pattern (also known as wrapper, an alternative naming shared with the decorator pattern) that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code. Wikipedia
W czym MOŻE pomóC? Dopasowanie dwóch niekompatybilnych interfejsów Przykrycie zewnętrznego kodu własnym interfejsem w celu jeszcze większego rozluźnienia zależności Praca ze starym kodem i przystosowanie go do nowego interfejsu
UWAGA! Dwa typy Adaptera: klasowy oraz obiektowy Adapter dwukierunkowy - każda z klas może pełnić funkcję zarówno klienta jak adaptowanej klasy Kod kliencki nie wie czy pracuje z klasą docelową bezpośrednio czy za pośrednictwem adaptera z klasą z niekompatybilnym interfejsem
Jakie ma wady ? Większe skomplikowanie kodu Zbyt częste tworzenie adapterów nawet jeśli nie jest to wymagane
FACADE
The facade pattern is a software-design pattern commonly used in object-oriented programming. Analogous to a facade in architecture, a facade is an object that serves as a front-facing interface masking more complex underlying or structural code. Wikipedia
W czym MOŻE pomóC? Ułatwienie korzystania z rozbudowanego, skomplikowanego interfejsu biblioteki, komponentu Podział na warstwy Czytelniejszy kod klienta Niezależny rozwój złożonego systemu schowanego za fasadą - klient ma stały interfejs fasady.
$konwerter = ( new ArtykulConverterFactory ()) -> createConverter ( $typArtykulu ) ; $konfiguracja = $konwerter -> zKreatora ( $konfiguracjaZKreatora ) ; $service = new IndeksService( $this -> typ , new IndeksArtykulModelFactory () ) ; $indeks = $this -> service -> utworz ( $konfiguracja ) ; …… $konwerter = ( new ArtykulConverterFactory ()) -> createConverter ( $pozycja -> typ ) ; $konfiguracja = $konwerter -> zZamowieniePozycje ( $pozycja ) ; $service = new IndeksService( $this -> typ , new IndeksArtykulModelFactory () ) ; $indeks = $this -> service -> utworz ( $konfiguracja ) ; PRZYKŁADOWY PROCES GENEROWANIA INDEKSU ARTYKULU
class IndeksFacade { protected $typ ; protected $konwerterFactory ; protected $service ; public function __construct ( int $typ , ArtykulConverterFactoryInterface $konwerterFactory , IndeksServiceInterface $service ) { $this -> typ = $typ ; $this -> konwerterFactory = $konwerterFactory ; $this -> service = $service ; } public function utworzZKreatora ( $konfiguracjaZKreatora ): Indeks { $konfiguracja = $this -> getKonwerter () -> zKreatora ($konfiguracjaZKreatora) ; return $this -> service -> utworz ($konfiguracja) ; } public function utworzZPozycji (AbstractPozycja $pozycja ): Indeks { $konfiguracja = $this -> getKonwerter () -> zPozycji ($pozycja) ; return $this -> service -> utworz ($konfiguracja) ; } public function getKonwerter (): ArtykulConverterInterface { return $this -> konwerterFactory -> createConverter ($this -> typ) ; } } Przykładowa IMPLEMENTACJA
Jakie ma wady ? Może się stać klasą-bogiem - posiadać silne powiązanie z bardzo dużą ilością innych klas aplikacji
OBSERVER
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. Wikipedia
W czym MOŻE pomóC? Powiadomienie zainteresowanych obiektów o zmianie stanu innego obiektu Implementacja powiadomień (events)
class ObserverA implements SplObserver { public function update (SplSubject $subject ) { echo 'Aktualizacja ' . __CLASS__ . PHP_EOL ; } } class ObserverB implements SplObserver { public function update (SplSubject $subject ) { echo 'Aktualizacja ' . __CLASS__ . PHP_EOL ; } } class Subject implements SplSubject { protected $observers = []; public function attach (SplObserver $observer ) { $this -> observers [spl_object_hash ($observer) ] = $observer ; } public function detach (SplObserver $observer ) { unset ($this -> observers [spl_object_hash ($observer) ] ) ; } public function notify () { foreach ($this -> observers as $observer) { $observer -> update ($this) ; } } } Przykładowa IMPLEMENTACJA
$foo = new Foo() ; // this handler is a global function $foo -> on (Foo:: EVENT_HELLO , 'function_name' ) ; // this handler is an object method $foo -> on (Foo:: EVENT_HELLO , [ $object , 'methodName' ]) ; // this handler is a static class method $foo -> on (Foo:: EVENT_HELLO , [ 'app\components\Bar' , 'methodName' ]) ; // this handler is an anonymous function $foo -> on (Foo:: EVENT_HELLO , function ( $event ) { // event handling logic }) ; YII2 events dokumentacja - przykład rejestracji obserwatorów
Jakie ma wady ? Obserwatorzy nie znają innych obserwatorów Kolejność powiadamiania jest niezależna
DEPENDENCY INJECTION
In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object. A "dependency" is an object that can be used, for example as a service. Instead of a client specifying which service it will use, something tells the client what service to use. Wikipedia
W czym MOŻE pomóC? Usunięcie sztywnych odwołań z kodu klas Rozluźnienie powiązań miedzy klasami. SOLID: Dependency Inversion Principle Oddzielenie procesu tworzenia obiektu od jego użycia Ułatwienie testowanie kodu, podmiana zależności Konfiguracja interfejsów programu za pomocą plików konfiguracyjnych
Uwaga! Dependency Injection jest jednym ze sposób implementacji Inversion of Control poprzez wstrzykiwanie zależności z poziomu: konstruktora, settera lub metody IoC jest paradygmatem programowania polegającym na odwróceniu kontroli. Zamiast samemu kontrolować kod frameworka, oddajemy kontrolę naszego kodu dla tego właśnie frameworka (w tym przypadku kontrolę nad tworzeniem obiektów) Dependency Injection Container jest biblioteką dostarczają funkcjonalność DI, która implementuje np. Tworzenie map zależności między interfejsami a ich konkretnymi implementacjami, odczytywanie zależności poprzez użycie Refleksji
ImplemENTACJE W PHP Symfony Dependency Injection Component Laravel Service Container Yii2 Dependency Injection Container PHP-DI ( php-di.org )
<?php class MadService extends BaseObject { protected $api ; public function __construct ( $config = []) { $this -> api = new MadApi () ; parent :: __construct ($config) ; } public function zamowieniaDoDostawcow () { return $this -> api -> zamowieniaDoDostawcow () ; } } Przykładowa IMPLEMENTACJA
<?php class MadService extends BaseObject { protected $api ; public function __construct (MadApiInterface $api , $config = []) { $this -> api = $api ; parent :: __construct ($config) ; } public function zamowieniaDoDostawcow () { return $this -> api -> zamowieniaDoDostawcow () ; } } Przykładowa IMPLEMENTACJA
<?php interface MadApiInterface { public function zamowieniaDoDostawcow (): array; } Przykładowa IMPLEMENTACJA
<?php //config-prod return [ MadApiInterface:: class => DI\create(MadApi:: class ) ] ; //config-dev return [ MadApiInterface:: class => DI\create(MadApiStub:: class ) ] ; //definicja kontenera np w bootstrapie aplikacji i integracja z frameworkiem $container = ( new DI\ContainerBuilder ()) -> addDefinitions ( 'config.php' ) -> build () ; class MadZamowienia extends Controller { public function actionIndex () { /** @var MadService $mad */ $mad = $container -> get ( 'MadService' ) ; $zamowienia = $mad -> zamowieniaDoDostawcow () ; … } } PrzykładowE UŻYCIE - PHP-DI
Jakie ma wady ? Ciężej zrozumieć jak działa klasa i skąd bierze zależności - w szczególności dla młodych developerów Obiekty są tworzone na początku działania aplikacji - możliwe problemy z wydajnością*
ŹRóDŁA, LITERATURA https://wikipedia.org https://stackoverflow.com/ Design Patterns: Elements of Reusable Object-Oriented Software. The "Gang of Four": Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides PHP Objects, Patterns and Practise, 5th Edition. Matt Zandstra Clean Code. Martin Robert C.