D v SOLIDe

Milan Herda, 03 / 2018

Príklady sú vlastné alebo prebrané z knihy

Principles of Package Design

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

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ť