D v SOLIDe
Milan Herda, 03 / 2018
Príklady sú vlastné alebo prebrané z knihy
Kto som
programátor (PHP, JavaScript), hráč spoločenských hier, fanúšik sci-fi, wannabe autor browser hier (feudarium.com)
Profesia, FatChilli, Porada, BlueOrange, NOV
profesia.sk, domelia.sk, rtvs.sk, reality.sme.sk, living.sk...
O čom budeme hovoriť
- Čo je SOLID
- Dependency Inversion Principle
- Výhody DIP
- Ako rozpoznať porušenia princípu
- Ako refaktorovať, aby sme dodržali princíp
SOLID
- Single Responsibility Principle
- Open-Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Skratka predstavujúca 5 základných princípov dobrého softvérového návrhu
Dependency Inversion Principle
Princíp obrátených závislostí
Ak váš kód potrebuje k svojej činnosti iný objekt (funkciu/triedu), tak tento objekt je závislosťou pre váš kód.
Čo je závislosť?
Najviac viditeľné sú parametre metód a property tried
Dependency Inversion Principle
- "objekt" na vyššej úrovni by nemal mať závislosť na nižšej úrovni
- "objekt" by mal závisieť iba na abstraktných "objektoch"
- "objekt" by nemal závisieť na konkrétnostiach
Prevedené do praxe v PHP
- Interface/Abstraktná trieda nemôže závisieť na konkrétnej triede
- Všetko by malo závisieť na interfacoch alebo abstraktných triedach
- Nič by nemalo mať konkrétnu triedu ako závislosť
Prečo?
Je jednoduchšie vymeniť inštanciu niečoho abstraktného, ako konkrétnu vec
Výhody dodržiavania
Dependency Inversion Principle
- časti kódu sú navzájom ľahko vymeniteľné
Symptómy porušenia princípu
- chýbajúce interfacy/abstraktné triedy
- interface nezávisí na interface, ale na konkrétnej triede
- abstraktná trieda závisí na konkrétnej triede
- konkrétna trieda závisí na konkrétnej triede namiesto interfacu/abstraktnej triede
Zdrojové súbory:
Príklad 1
class JurajSiroky
{
// ...
}
interface StatnaZakazka
{
public function vyberDodavatela(
JurajSiroky $dodavatel
);
}
interface DodavatelSplnajuciPodmienky
{
// ...
}
interface StatnaZakazka
{
public function vyberDodavatela(
DodavatelSplnajuciPodmienky $dodavatel
);
}
Nie je to takto lepšie?
Príklad 2
class Authentication
{
private ConnectionInterface $connection;
public function __construct(ConnectionInterface $connection)
{
$this->connection = $connection;
}
public function checkCredentials(string $username, string $password)
{
$user = $this->connection->fetchAssoc(
'SELECT * FROM users WHERE username = ?',
[$username]
);
if ($user === null) {
throw new InvalidCredentialsException('User not found');
}
// validate password
// ...
}
}
Potrebuje autentifikácia vedieť, kde sú uložené používateľské dáta?
Je možné mať používateľské dáta uložené inde ako v databáze?
class Authentication
{
private UserProviderInterface $userProvider;
public function __construct(UserProviderInterface $userProvider)
{
$this->userProvider = $userProvider;
}
public function checkCredentials(string $username, string $password)
{
$user = $this->userProvider->findUser($username);
if ($user === null) {
throw new InvalidCredentialsException('User not found');
}
// validate password
// ...
}
}
interface UserProviderInterface
{
public function findUser($username);
}
class DoctrineDbalUserProvider implements UserProviderInterface
{
// ...
}
class TextFileUserProvider implements UserProviderInterface
{
// ...
}
Dependency Inversion Principle
Princíp obrátených závislostí
Dependency Inversion Principle
- "objekt" na vyššej úrovni by nemal mať závislosť na nižšej úrovni
- "objekt" by mal závisieť iba na abstraktných "objektoch"
- "objekt" by nemal závisieť na konkrétnostiach
Prevedené do praxe v PHP
- Interface/Abstraktná trieda nemôže závisieť na konkrétnej triede
- Všetko by malo závisieť na interfacoch alebo abstraktných triedach
- Nič by nemalo mať konkrétnu triedu ako závislosť
Prečo?
Je jednoduchšie vymeniť inštanciu niečoho abstraktného, ako konkrétnu vec
Voľné programovanie
Naprogramujte tzv. FizzBuzz generátor, ktorý:
- generuje zoznam celých čísel od 1 po n
- čísla deliteľné 3 nahradí reťazcom "Fizz"
- čísla deliteľné 5 nahradí reťazcom "Buzz"
- čísla deliteľné 3 aj 5 nahradí reťazcom "FizzBuzz"
- na každé číslo sa vždy aplikuje maximálne jedno pravidlo
Upravte váš FizzBuzz generátor:
- pridajte pravidlo nahradzujúce čísla deliteľné 7 reťazcom Bar
- pridajte pravidlo nahradzujúce číslo 11 reťazcom "jedenásť"
- pridajte pravidlo vymieňajúce číslicu 4 za písmeno A
- vymeňte poradie pravidiel
- pridajte nové pravidlo bez potreby zmeny kódu generátoru
interface RuleInterface
{
public function matches(int $number): bool;
public function getReplacement(): mixed;
}
class FizzRule implements RuleInterface
{
public function matches(int $number): bool
{
return $number % 3 === 0;
}
public function getReplacement(): mixed
{
return 'Fizz';
}
}
class FizzBuzzGenerator
{
private $rules = [];
public function addRule(RuleInterface $rule)
{
$this->rules[] = $rule;
}
public function generateList(int $limit): array
{
$list = [];
for ($number = 1; $number <= $limit; $number++) {
$list[] = $this->generateElement($number);
}
return $list;
}
private function generateElement(int $number): mixed
{
foreach ($this->rules as $rule) {
if ($rule->matches($number)) {
return $rule->getReplacement();
}
}
return $number;
}
$fizzBuzz = new FizzBuzzGenerator();
$fizzBuzz->addRule(new FizzBuzzRule());
$fizzBuzz->addRule(new FizzRule());
$fizzBuzz->addRule(new BuzzRule());
...
$list = $fizzBuzz->generateList(100);
Riešenie:
Opakovanie
SOLID - Záver
Aby bol kód ľahko udržiavateľný, rozširovateľný, testovateľný a rozširovateľný s minimálnym množstvom programovania, tak každá trieda by mala:
- riešiť iba jednu vec
- byť otvorená pre zmenu správania bez potreby zmeny kódu
- byť dobrým potomkom svojich rodičov
- implementovať a závisieť na malých a pre klientov špecifických interfejsoch
- závisieť na abstrakciách a nie konkrétnostiach
Viac informácií:
Matthias Noback - Principles of Package Design
Ďakujem za pozornosť
D v SOLIDe
By Milan Herda
D v SOLIDe
- 615