Wzorce projektowe w praktyce

PHPstokPHPstok 197 views 71 slides Mar 17, 2020
Slide 1
Slide 1 of 71
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71

About This Presentation

Zastosowanie wybranych wzorców projektowych w praktyce na przykładzie działającej aplikacji oraz wybranych frameworków PHP


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”

Wzorce projektowe wg bandy czworga - Kreacyjne Abstract Factory Builder Factory Method Prototype Singleton

Wzorce projektowe wg bandy czworga - STRUKTURALNE Adapter Bridge Composite Decorator Facade Flyweight Proxy

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

DOSTEP GLOBALNY PRZYKŁADY Yii2 - logger, komponenty, DI (Yii::error(), Yii::$app->user) Laravel - helpery, facady (config(), view(), Cache::get())

SIMPLE Factory

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

<?php use Trello\Client ; $client = new Client() ; $client -> authenticate ( Yii:: $app -> params [ 'trello_api_key' ] , Yii:: $app -> params [ 'trello_token' ] , Client:: AUTH_URL_CLIENT_ID ) ; $client -> cards () -> create ( array_merge ($parametry , [ 'idList' => $listaID ] ) ) ; WYWOŁANIE BEZPOŚREDNIE KODU BIBLIOTEKI

<?php class Trello extends Component implements TrelloInterface { public $apiKey ; public $token ; /** @var Client $client */ protected $client ; public function __construct (Client $client , $config = []) { $this -> client = $client ; parent :: __construct ($config) ; } public function init () { parent :: init () ; $this -> polacz () ; } public function dodajKarteDoListy ( string $listaID , array $parametry ) { return $this -> client -> cards () -> create ( array_merge ($parametry , [ 'idList' => $listaID ] ) ) ; } protected function polacz () { $this -> client -> authenticate ($this -> apiKey , $this -> token , Client:: AUTH_URL_CLIENT_ID) ; } } Przykładowa IMPLEMENTACJA

Yii:: createObject ( Trello:: class ) -> dodajKarteDoListy ( $listaID , $parametry ) ; PrzykładowE UŻYCIE

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

Yii:: createObject ( IndeksFacade:: class, [ $typ ] ) -> utworzZKreatora ( $konfiguracjaZKreatora ) ; Yii:: createObject ( IndeksFacade:: class, [ $typ ] ) -> utworzZPozycji ( $pozycja ) ; PrzykładowE UŻYCIE

Laravel FASADY Illuminate\Support\Facades\ Route::get() Illuminate\Support\Facades\ View::make() Illuminate\Support\Facades\ Validator::make() …

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

$subject = new Subject() ; $observerA = new ObserverA() ; $observerB = new ObserverB() ; $subject -> attach ( $observerA ) ; $subject -> attach ( $observerB ) ; $subject -> notify () ; // Aktualizacja ObserverA // Aktualizacja ObserverB PrzykładowE UŻYCIE

<?php class TrelloNotyfikatorHandler implements NotyfikatorHandlerInterface { public function wyslij (MessageEvent $event ) { Yii:: createObject ( Trello:: class ) -> dodajKarteDoListy ( 'zgloszenia' , $event -> message) ; } } class Zgloszenie extends ActiveRecord { const NOWE_ZGLOSZENIE_EVENT = 'noweZgloszenie' ; public function create () { if ($this -> save ()) { $this -> trigger ( self :: NOWE_ZGLOSZENIE_EVENT , new MessageEvent ( [ 'message' => 'Nowe zgłoszenie o treści: ' . $this -> tresc ] )) ; return true; } return false; } } Przykładowa IMPLEMENTACJA - YII2

class ZgloszenieController extends \yii\web\Controller { protected $notyfikator ; public function __construct ( $id , $module , $config = [] , NotyfikatorHandlerInterface $notyfikator ) { $this -> notyfikator = $notyfikator ; parent :: __construct ($id , $module , $config) ; } public function actionCreate () { $model = new Zgloszenie () ; $model -> on ( Zgloszenie:: NOWE_ZGLOSZENIE_EVENT , [ $this -> notyfikator , 'wyslij' ] ) ; $model -> create () ; } } PrzykładowE UŻYCIE - YII2

<?php return [ Watek:: class . '@' . Watek:: EVENT_NOWY_WATEK_DO_ADMINA => [ TrelloNotyfikatorHandler:: class, EmailNotyfikatorHandler:: class, PushNotyfikatorHandler:: class, ] , Zlecenie:: class . '@' . Zlecenie:: EVENT_AFTER_INSERT => [ NoweZlecenieHandler:: class, ] , Zlecenie:: class . '@' . Zlecenie:: EVENT_AFTER_PRZYJECIE => [ PrzyjecieZleceniaHandler:: class, ] , ] ; … \yii\base\Event:: on (Watek:: class, Watek:: EVENT_NOWY_WATEK_DO_ADMINA , [ TrelloNotyfikatorHandler:: class, 'handle' ]) ; DEFINICJA OBSERWATORÓW NA POZIOMIE KLAS

$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 class MadApi implements MadApiInterface { const SOAP_SERVER_LOGIN = ‘’ ; protected $client ; public function zamowieniaDoDostawcow (): array { $result = $this -> client -> ZdDocListGet ( [ 'ZdDocListGetData' => [ 'ClientCode' => self :: SOAP_SERVER_LOGIN , ] , ] ) ; if ($result -> ArrayZdDocListGetResult -> Status !== 1 ) { return [ 'status' => 'error' , 'massage' => $result -> ArrayZdDocListGetResult -> ErrorMessage , ] ; } return [ 'status' => 'success' , 'massage' => $result -> ArrayZdDocListGetResult -> ZdDocListGetResult , ] ; } } Przykładowa IMPLEMENTACJA

<?php class MadApiStub implements MadApiInterface { public function zamowieniaDoDostawcow (): array { \Yii:: debug ( 'Pobieram dane z API MAD' , __METHOD__) ; return [ 'status' => 'success' , 'massage' => [] , ] ; } } Przykładowa IMPLEMENTACJA

<?php //config-prod $config[ 'container' ] [ 'definitions' ] = [ 'components\MadApiInterface' => 'interfaces\MadApi' ] ; //config-dev $config[ 'container' ] [ 'definitions' ] = [ 'components\MadApiInterface' => 'interfaces\MadApiStub' ] ; class MadZamowienia extends \yii\web\Controller { public function actionIndex () { /** @var MadService $mad */ $mad = \Yii:: createObject ( MadService:: class ) ; $zamowienia = $mad -> zamowieniaDoDostawcow () ; … } } PrzykładowE UŻYCIE - YII2

<?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.

DZĘKUJE SMJS