5. 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
6. CZYM JEST WZORZEC PROJEKTOWY
▸Ogólnym rozwiązaniem często spotykanego problemu
projektowego.
▸Szablonem rozwiązania problemu
▸Sformalizowanym elementem najlepszych praktyk
programistycznych
7. 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*
8. 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ł*
14. 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
15. W CZYM MOŻE POMÓC?
▸Jedna instancja klasy podczas działania aplikacji
▸Globalny dostęp do w/w instancji*
▸Ulepszona wersja zmiennej globalnej
16. <?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
17. <?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
18. 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.
19. <?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ŁADOWEUŻYCIE
20. 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
21. <?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ŁADOWEUŻYCIE-
ZAMIASTSINGLETONA-DI
25. 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
26. 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
27. <?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
28. 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ŁADOWEUŻYCIE
29. JAKIE MA WADY ?
▸Większa złożoność kodu
▸Większa ilość klas
▸Wersja simple factory łamię zasadę Open Close Principle
(SOLID).
30. <?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
32. 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
33. 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
34. 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
35. <?php
use TrelloClient;
$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ŁANIEBEZPOŚREDNIE
KODUBIBLIOTEKI
36. <?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
40. 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
41. 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.
42. $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ŁADOWYPROCES
GENEROWANIAINDEKSUARTYKULU
43. 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
48. 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
49. W CZYM MOŻE POMÓC?
▸Powiadomienie zainteresowanych obiektów o zmianie stanu
innego obiektu
▸Implementacja powiadomień (events)
50. 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
51. $subject = new Subject();
$observerA = new ObserverA();
$observerB = new ObserverB();
$subject->attach($observerA);
$subject->attach($observerB);
$subject->notify();
// Aktualizacja ObserverA
// Aktualizacja ObserverB
PRZYKŁADOWEUŻYCIE
52. <?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
53. class ZgloszenieController extends yiiwebController
{
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ŁADOWEUŻYCIE-YII2
55. $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, ['appcomponentsBar', 'methodName']);
// this handler is an anonymous function
$foo->on(Foo::EVENT_HELLO, function ($event) {
// event handling logic
});
YII2EVENTSDOKUMENTACJA-PRZYKŁAD
REJESTRACJIOBSERWATORÓW
56. JAKIE MA WADY ?
▸Obserwatorzy nie znają innych obserwatorów
▸Kolejność powiadamiania jest niezależna
58. 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
59. 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
60. 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
61. IMPLEMENTACJE W PHP
▸Symfony Dependency Injection Component
▸Laravel Service Container
▸Yii2 Dependency Injection Container
▸PHP-DI (php-di.org)
62. <?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
63. <?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
68. <?php
//config-prod
return [
MadApiInterface::class => DIcreate(MadApi::class)
];
//config-dev
return [
MadApiInterface::class => DIcreate(MadApiStub::class)
];
//definicja kontenera np w bootstrapie aplikacji i integracja z frameworkiem
$container = (new DIContainerBuilder())
->addDefinitions('config.php')
->build();
class MadZamowienia extends Controller
{
public function actionIndex()
{
/** @var MadService $mad */
$mad = $container->get('MadService');
$zamowienia = $mad->zamowieniaDoDostawcow();
…
}
}
PRZYKŁADOWEUŻYCIE-PHP-
DI
69. 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ą*
Zanim przejdę dalej chciałbym odnieść się do prezentacji koleżanki. Senior developer rozwija się między innymi poprzez dzielenie się więdzą. Ja ten wykład potraktuję jako sprawdzian czy się nadaje do tego.
Wybrałem te wzorce (oprócz Singletona) ponieważ najczęściej są spotykane w mojej pracy oraz są bardzo popularne w PHPie wg danych z internetu
Temat wzorców zostanie zaprezentowany w sposób praktyczny z mojego punktu widzenia
Nie będzie definicji stricte akademickich i UMLi
Treść tego wykładu kieruje do programistów którzy chcą zacząć swoją przygodę z wzorcami lub ją zaczęli ale mają problemy z ich stosowaniem
Wzorce otaczają nas praktycznie na każdym kroku. Jeżeli używamy jakiegolwiek topowego frameworka w PHP to używamy tam świadomie bądź mniej zaimplementowanych wzorców projektowych (Yii2, Laravel, Symfony)
!!! Nie mów tego Pominąłem świadomie jeden bardzo ważny wzorzec Strategy ponieważ uważam że powinno się go omówić bardzo dokładnie przez potrzeba o wile więcej czasu
30 sekund
Wzorzec wg GoF składa się z czterech elementów: nazwy wzorca, problemu, rozwiązania, konsekwencji
Dobre praktyki jak SOLID KISS DRY idą w parze właśnie z wzorcami projektowymi
Implementacje wzorców różnią się w stosunku do różnych języków programowania.
Na koniec wykładu pokażę Wam controller z naszego projektu a propo braku dobrych praktyk :)
Tak samo jak bez innych dobrych praktyk tylko jak łatwo będzie go utrzymywać, testować to już inna kwestia
Wzorce projektowe tzw Bandy Czworga ogólnie są uważana za podstawę dla wszystkich innych wzorców.
Dla tego moją prezentacją w pewnej części opieram właśnie o ich definicje
Wzorce projektowe zostały spopularyzowane w 1995 (copyright) przez Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson oraz John Vlissides) dzięki książce Wzorce projektowe: Elementy oprogramowania obiektowego wielokrotnego użytku
Wzorce zostały zdefiniowane i opisane na podstawie języków silnie typowany, zorientowanych obiektowo (C++, Java, C#…)
Słowa krytyki
Wzorce projektowe rozwiązują słabości języka programowania więc dla czego nie zmienić właśnie języka ?
Peter Norvig - powiedział że 16 z 23 wzorców (skupionych na C++) mogą zostać wyeliminowane poprzez zmianę języka
Definiują proces tworzenia nowych obiektów oraz ich konfiguracji
Definiują struktury powiązanych ze sobą obiektów
Definiują zachowanie i odpowiedzialność współpracujących ze sobą wzorców
Zanim przejdziemy dalej chciałbym powiedzieć, że na prawdę miałem problemy z znalezieniem własnych przykładów ponieważ praktycznie nie stosuję tego wzorca a korzystam z niego z dostarczonych bibliotek / frameworków
Konstruktor - blokujemy możliwość tworzenia instancji w tradycyjny sposób
Clone - blokujemy możliwość klonowania obiektu
Wakeup - blokujemy możliwość powstania nowej instancji podczas deserializacji obiektu
Single Responsibility Principle - klasa ma mieć jeden powód do zmiany i tylko jeden
Dependency Inversion Principle - Moduły wyższego poziomu nie powinny zależeć od modułów niższego poziomu a od abstrakcji. Klasy mają być zależne od abstrakcji nie od konkretów
Logger odwołuje się do bazy w testach mamy problem
Trello odwołuje się do serwerów trello podczas prac developerskich możemy tworzyć niechciane karty
Jak wykonać test jednostkowy metody create() w izolowanym środowisku
MOKI
3) Yii::error(), Yii::info()
Service locator również powoduje dostęp globalny ale umożliwia podmianę implementacji metody getta do komponentów
3) Yii::error(), Yii::info()
Simple factory nie jest wymieniony w zestawieniu GoF
Oficjalnymi wzorcami wytwórców są: Factory Method oraz Abstract Factory
Część developerów uważa, że nie jest to wzorzec projektowy
Enkapsulacja (hermetyzacją)
Ograniczanie bezpośredniego dostępu do danych i logiki obiektów
Wiązanie danych z metodami (funkcjami) operującymi na tych danych
Konwertują różne struktury przechowywania parametrów artykułów na jeden wspólny. Każdy artykuł ma unikalny zestaw parametrów oraz wykonuje różne akcje aby zwrócić pożądany parametr w danym formacie.
Open Close Principle - Klasa powinna być otwarta na rozszerzenia lecz zamknięta na modyfikacje
Kiedy mamy nowy typ np. Artykułu musimy edytować kod i dodać nową opcję do switch’a
Adapter obiektowy wykorzystuje kompozycje.
Klasowy wykorzystuje dziedziczenie is-a a obiektowy kompozycje has-a
$client jest obiektem adaptowanym do interfejsu TrelloInterface
Obserwatorzy mogą obserwować ale nie muszą
SPL - Standard PHP Library
Podczas powiadamiania obserwatorzy otrzymują także referencję do obiektu obserwowanego.
Jeden obserwator może obserwować kilka innych obiektów, a jeden obiekt obserwowany może być obserwowany przez kilku obserwatorów.
Deklarujemy klasy obserwatorów (observer, listener)
Deklarujemy klasę obserwowaną (subject)
Implementujemy metody dodawania usuwania i powiadamiania obserwatorów z poziomu klasy obserwowanej
TrelloNotifikatorHandler to jest nasz obserwator
Zgloszenie jest obiektem obserwowanym
Metoda trigger == notify z SPL
Różnica w implementacji: metoda notify z SPLSubject przekazywała do każdego wywołania update obserwatora referencję do swojej instancji (obserwowanego obiektu)
Yii2 do obserwatorów przekazuje obiekt Event który posiada referencje do obiektu obserwowanego (w tym przypadku Zgłoszenie) w parametrze sender.
Klasa Event została rozszrzona o parametr message aby był wspólny dla calej rodziny klas Notyfikatorów
Metoda modelu ON == attach (detach == Yii OFF)
Rejestrowanie obserwatorów na poziomie klas a nie obiektów
Tylko jeden sposób jest najbardziej zbliżony do oryginalnego sposobu dołączania obserwatorów do obserwowanego obiektu.
Dependency Inversion Principle: klasy wyższego poziomu nie powinny zależeć od klas niższego poziomu tylko od abstrakcji
PHP DI od wersji 6 tworzy zależności i przechowuje ich jedne instancje tak jak singleton
Get zwraca singletona, make - tworzy go za każdym razem
Mozna tak skonfigurować Di aby tworzyło wszystkie zależności przed ich zapotrzebowaniem EAGER
LAZY - na żądanie