The document discusses the SOLID principles of object-oriented design:
- The Single Responsibility Principle states that a class should have one single responsibility.
- The Open/Closed Principle states that software entities should be open for extension but closed for modification.
- The Liskov Substitution Principle states that objects should be replaceable with their subtypes without altering correctness.
- The Interface Segregation Principle states that many specific client interfaces are better than one general interface.
- The Dependency Inversion Principle states that high-level modules shouldn't depend on low-level modules but both should depend on abstractions.
3. “On the one hand, if the bricks aren’t well made, the architecture of the building
doesn’t matter much. On the other hand, you can make a substantial mess with
well-made bricks.”
Robert C. Martin
4. SOLID
SOLID is a acronym for five design principles intended to make software
designs more understandable, flexible and maintainable.
● Zebrane przez Robert C. Martin
● Nazwane przez Michael Feathers w 2004
5. S R P
O C P
L S P
I S P
D I P
Single Responsibility Principle
Open/Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
7. SOLID
Single Responsibility Principle
“A class should only have a single responsibility, that is, only changes to one
part of the software's specification should be able to affect the specification of
the class.”
14. SOLID
class Publisher
{
public function display($type)
{
switch($type) {
case 'screen':
/* some logic */
break;
case 'rss':
/* some logic */
break;
}
}
}
$publsher = new Publisher();
$publsher->display('screen');
$publsher->display('rss');
15. SOLID
interface PublisherInterface
{
public function getContent();
}
class Publisher
{
public function display(PublisherInterface $publisher)
{
$publisher->getContent();
}
}
class ScreenPublisher implements PublisherInterface
{
public function getContent() { /* some logic */ }
}
class RssPublisher implements PublisherInterface
{
public function getContent() { /* some logic */ }
}
16. SOLID
$publsher = new Publisher();
$screenPublisher = new ScreenPublisher();
$publsher->display($screenPublisher);
$rssPublisher = new RssPublisher();
$publsher->display($rssPublisher);
class PrintPublisher implements PublisherInterface
{
public function getContent() { /* some logic */ }
}
$printPublisher = new PrintPublisher();
$publsher->display($printPublisher);
20. SOLID
class Ptak
{
public function lec() { /* some logic */ }
}
class Golab extends Ptak
{
}
class Strus extends Ptak
{
public function lec()
{
throw new Exception('Not Implemented');
}
}
class Ptak
{
}
class LatajacyPtak extends Ptak
{
public function lec() { /* some logic */ }
}
public class Golab extends LatajacyPtak
{
}
public class Strus extends Ptak
{
}
21. SOLID
interface Jednoslad
{
public function jedz();
}
class Rower implements Jednoslad
{
public function jedz();
}
$notebook = new Rower();
$notebook->jedz();
class Motor implements Jednoslad
{
public function wlacz()
{
/* some logic */
}
public function jedz()
{
/* some logic */
}
}
$tablet = new Motor();
$tablet->jedz();
// throw new Exception("You need to start the engine")
25. SOLID
interface FormatterInterface
{
public function toPDF($content);
public function toXML($content);
public function toJSON($content);
}
class Formatter implements FormatterInterface
{
public function toPDF($content)
{
throw new Exception('Not Implemented');
}
public function toXML($content)
{
/* some logic */
}
public function toJSON($content)
{
/* some logic */
}
}
26. SOLID
interface PdfFormatterInterface
{
public function toPDF($content);
}
interface XmlFormatterInterface
{
public function toXML($content);
}
interface JsonFormatterInterface
{
public function toJSON($content);
}
class Formatter implements JsonFormatterInterface, XmlFormatterInterface
{
public function toJSON($content)
{
/* some logic */
}
public function toXML($content)
{
/* some logic */
}
}
31. SOLID
class MysqlConnection
{
public function connect()
{
/* some logic */
}
}
class PasswordReminder
{
private $connection;
public function __construct(MysqlConnection $connection)
{
$this->connection = $connection;
}
}
32. SOLID
interface DbConnectionInterface
{
public function connect();
}
class MysqlConnection implements DbConnectionInterface
{
public function connect()
{
/* some logic */
}
}
class PasswordReminder
{
private $connection;
public function __construct(DbConnectionInterface $connection)
{
$this->connection = $connection;
}
}
33. Podsumowanie
● klasa powinna mieć jeden i tylko jeden powód do zmiany
● pozwól na zmianę zachowania systemu poprzez dodawanie nowego kodu,
a nie modyfikowanie istniejącego
● buduj elementy tak aby były wzajemnie wymienne
● unikaj tworzenia zależności od elementów których nie potrzebujesz
● uniezależnij od siebie kod implementujący poszczególne warstwy
programista PHP
obecnie pracuję w firmie Aurora Creation sp. z o.o.
zajmuję się pracą ze sklepami na platformie Magento
tutaj są moje dane kontaktowe
na wstępie chciałbym jeszcze dodać że nie mam doświadczenia w publicznych prezentacjach, dlatego proszę o wyrozumiałość
oraz że będę posiłkował się notatkami
kiedy szef poprosił aby powiedzieć kilka słów na PHPStock’u
musiałem się zastanowić nad tematem o którym chciałbym powiedzieć
jednym z popularnych tematów jest SOLID
nie jest to temat nowy i na pewno wielu z was doskonale go zna
Nie był on omawiany na PHPStock’u dlatego wypada nadrobić to niedopatrzenie
Zaczynamy
na początek cytat
z jednej strony jeżeli cegiełki nie są solidnie zbudowane to architektura nie ma większego znaczenia. Z drugiej, można zrobić niezły bałagan z dobrze zbudowanych cegiełek
to cytat z książki Roberta C. Martina (Uncle Bob)
dość dobrze opisuje to z czym stykamy się na co dzień
wiadomo że dobrą architekturę poznaje się nie po tym jak aplikacja działa w momencie puszczenia live a po tym jak znosi zmiany wymagań w trakcie wieloletniego procesu utrzymania
czasem trzeba się dobrze nagłowić nad strukturą naszej aplikacji
SOLID został stworzony aby pomóc nam tworzyć dobrą architekturę
SOLID to akronim nazw 5 zasad traktujacych o dobrych praktykach tworzenia oprogramowania obiektowego
Zostały zebrane przez Robert C. Martin pomiędzy końcówką lat ‘80 a początkiem lat 2000
nazwa SOLID została zaproponowana przez Michael’a Feather w mailu ok 2004 roku
SOLID ma pomóc tworzyć kod który będzie dobrze znosił zmiany, będzie łatwiejszy w utrzymaniu i prostszy do zrozumienia
SOLID to tak naprawdę trochę więcej liter
jak wspomniałem to zbiór 5 zasad
Nie są one czymś czego bezwzględnie trzeba przestrzegać, ale ich przestrzeganie pozwala budowa lepsze aplikacje
teraz przejdziemy do omówienia poszczególnych reguł
Każdy moduł/klasa powinny mieć jeden i tylko jeden powód do zmiany.
Każda klasa powinna odpowiadać przed jednym aktorem systemu, tak aby zmiany wymagań jednego aktora nie wpływały na to jak działa aplikacja dla innych aktorów
Załóżmy że mamy w firmie dział kadr, księgowości oraz techniczny.
Programując system POWIĄZALIŚMY klasę Emploee z każdym z tych działów.
Księgowość -> calculatepay
Kadry -> reportHours
Techniczny -> save
W pewnym momencie księgowość uznała że trzeba zmienić sposób obliczania godzin
Programista wprowadza zmiany w klasie regularHours
Prowadzi to do niezamierzonej zmiany funkcjonowania metody reportHours
Przykład kodu
Klasa Menu
Odpowiedzialna za pobranie i przeparsowanie zasobów z jakiegoś zewnętrznego źródła
Na załączonym przykładzie klasa posiada metody
Download - odpowiedzialną za pobranie danych
Parse - odpowiedzialną za przeparsowanie pobranych danych
Log - odpowiedzialną za zapisanie błędu
Na tym przykładzie jedna klasa ma wiele odpowiedzialności
W związku z tym zmiana dowolnej z tych funkcji może powodować błędy w pozostałych
Na kolejnym przykładzie
Poszczególne odpowiedzialności zostały wydzielone
Do klas XmlParser oraz HttpClient
Błędy natomiast są zwracane jako wyjątki
Dzięki temu poszczególne funkcjonalności zostały rozdzielone
I zmiana w klasie XmlParser nie wpłynie na działanie pozostałych elementów
Otwarty na rozszerzenie / zamknięty na modyfikację
Reguła rozsławiona przez Bertrand Meyer w latach ‘80
“Jeżeli oprogramowanie ma pozwalać na łatwe wprowadzanie zmian, to musi zostać zaprojektowane tak, żeby pozwalało na zmianę zachowania systemu poprzez dodawanie nowego kodu, a nie modyfikowanie istniejącego.”
Załóżmy że piszemy klasę prezentującą jakąś treść, jakiś kontent
Na początku ma ona być wyświetlane na ekranie
Po pewnym czasie wymagania stawiane przed naszą klasą są rozszerzane i ma obsłużyć również drukarkę
Dodajemy ify
Po kolejnej zmianie wymagań klasa ma obsłużyć również kanał RSS
Dodajemy ify
Każda zmiana wymagała od nas powrotu do kodu i jego zmodyfikowania
Uniknęlibyśmy tego gdybyśmy logikę odpowiedzialną za prezentacje danych wydzielili do oddzielnych klas
A klasę menu zaprojektowali tak aby ich używała
Przykład kodu
Mamy klasę Publisher której zadaniem jest zaprezentowanie informacji w odpowiednim formacie
Logika odpowiedzialna za sformatowanie danych została zawarta w prostym Switch’u
Zmusza nas to do zmodyfikowania tego swicha za każdym razem gdy chcemy dodać nowy format
Kolejny przykład definiuje interfejs
Oraz zmusza klasę Publisher do pracy z interfejsem
Logikę odpowiedzialną za formatowanie umieszczamy w klasach implementujących nasz interfejs
Sposób w jaki mają zostać sformatowane dane przekazujemy z zewnątrz
Dzięki temu dodanie nowego formatu ogranicza się do utworzenia klasy implementującej nasz interfejs
W taki sposób uniknęliśmy modyfikowania istniejącego kodu przy dodawaniu nowych funkcji
Powinna istnieć możliwość zastąpienia klas ich potomkami w taki sposób aby nie psuło to aplikacji
Reguła liskov mówi nam o tym że elementy aplikacji powinny być na tyle wymienialne że możemy wziąć sobie jedną klasę, wstawić w to miejsce inną i wszystko powinno działać
Załóżmy że do restauracji codziennie o 8 rano przywożą owoce, dzięki temu od tej godziny kucharz może przygotowywać dania gościom
Któregoś dnia właściciel podpisał umowę z innym dostawcą, ale zapomniał powiedzieć mu że warzywa mają przyjechać o 8
Firma nie dostarczyła warzyw, kucharz nie miał jak przygotować potraw gościom
Stało się tak bo został złamany kontrakt
Gydby dostawa przyjechała na czas to wszystko działało by jak dawniej
Unikaj tworzenia zależności od elementów których nie potrzebujesz
Zgodnie z tą zasadą - tworzone interfejsy powinny zawierać minimalną liczbę deklaracji metod.
Mniejsza ilość metod może być zrekompensowana możliwością dziedziczenia wielu interfejsów
Najtrudniej było mi wymyślić przykład do tej zasady
Każdy z nas ma klucze
Ja swoje nosze w kieszeni
Gdy interfejs ma wiele metod
To implementując go musimy zaimplementować wszystkie jego metody
Nie da się wydzielić kilku z nich
Tak jak na przykładzie - musimy zaimplementować niewspieraną metodę toPDF
Jeżeli natomiast mamy kilka interfejsów zawierających mniejszą liczbę metod
To możemy dowolnie kształtować to co implementuje nasza klasa
Klasy powinny zależeć od abstrakcji nie od konkretnych implementacji.
Jeżeli Klasa 2 zależy od Klasy 1 to nie mamy możliwości zmienić jej implementacji
Jeżeli natomiast Klasa 2 Będzie zależeć od interfejsu
To możemy swobodnie podmienić Klasę 1 na inną klasę implementującą ten sam interfejs.
nie mogłem się powstrzymać aby umieścić tu ten rysunek
to ilustracja do wiersza Juliama Tuwima a nie do popularnego teleturnieju
i z rzepki nie wystrzelą pieniążki
Kto pamięta ten wiersz to wie że w ostatniej zwrotce wszystkie postacie leżały na ziemi
Tak samo i my możemy leżeć jeżeli szef poprosi o wymianę klasy która leży w fundamentach systemu
A która nie posiada abstrakcji
Dla przykładu
Klasa PasswordReminder zależy od MysqlConnection
To znaczy że do konstruktora PasswordReminder zawsze musimy wrzucić MysqlConnection
Jeżeli jednak utworzymy interfejs DbConnectionInterface
I klasę PasswordReminder uzależnimy od tego interfejsu
To do konstruktora będziemy mogli podać dowolną klasę implementującą ten interfejs.
Innymi słowy “Nasza klasa klasę PasswordReminder będzie niezależna od konkretnej implementacji interfejsu DbConnectionInterface”
Zasada odwracania zależności to zwieńćzenie całego SOLID.
Zachęca do uzależniania się od abstrakcji, a nie od konkretnych implementacji rozwiązań.