D v SOLIDe

08/2016 / Milan "perún" Herda / @moriquend / prezentacie.perunhq.org

Príklady prebrané z knihy Principles of Package Design

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 a 5 zároveň nahradí reťazcom "FizzBuzz"

O čom budeme hovoriť

  • Čo je SOLID
  • Dependency Inversion Principle
  • Výhody Dependency Inversion Principle
  • Ako rozpoznať porušenia princípu

SOLID

Skratka predstavujúca 5 základných princípov dobrého softvérového návrhu

  • Single Responsibility Principle
  • Open-Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Dependency Inversion Principle

Princíp obrátených závislostí

Čo je závislosť

Ak váš kód potrebuje k svojej činnosti iný objekt (funkciu/triedu), tak tento objekt je závislosťou pre váš kód.

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étny objekt ako závislosť

Prečo?

Je jednoduchšie vymeniť inštanciu niečoho abstraktného ako konkrétnu vec

Výhody 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

Ktorý zápis interfacu sa vám zdá správnejší?


// riesenie 1
class Siroky { /* ... */ }

interface StatnaZakazka
{
    public function akceptujDodavatela(
        Siroky $dodavatel
    );
}

// riesenie 2
interface SplnaPodmienkyInterface { /* ... */ }

interface StatnaZakazka
{
    public function akceptujDodavatela(SplnaPodmienkyInterface $dodavatel);
}
                    

Príklad 2

Upravte váš FizzBuzz generátor:

  • pridajte pravidlo nahradzujúce čísla deliteľné 7 reťazcom Bar
  • vymyslite a pridajte nové pravidlo
  • vymeňte poradie pravidiel
  • upravte generátor, aby spĺňal Open-Closed Principle

Riešenie (pravidlá)


interface RuleInterface {
    public function matches($number);
    public function getReplacement();
}

class FizzRule {
    public function matches($number) {
        return $number % 3 === 0;
    }

    public function getReplacement() {
        return 'Fizz';
    }
}
                    

Riešenie (generátor)


class FizzBuzzGenerator {
    private $rules = [];

    public function addRule(RuleInterface $rule) {
        $this->rules[] = $rule;
    }

    public function generateList($limit) {
        $list = [];

        for ($number = 1; $number <= $limit; $number++) {
            $list[] = $this->generateElement($number);
        }

        return $list;
    }

    private function generateElement($number) {
        foreach ($this->rules as $rule) {
            if ($rule->matches($number)) {
                return $rule->getReplacement();
            }
        }

        return $number;
    }
                    

Riešenie (použitie)


$fizzBuzz = new FizzBuzz();

$fizzBuzz->addRule(new FizzBuzzRule());
$fizzBuzz->addRule(new FizzRule());
$fizzBuzz->addRule(new BuzzRule());
...
$list = $fizzBuzz->generateList(100);
                    

Príklad 3


use Doctrine\DBAL\Connection;

class Authentication {
    private $connection;

    public function __construct(Connection $connection) {
        $this->connection = $connection;
    }

    public function checkCredentials($username, $password) {
        $user = $this->connection->fetchAssoc(
            'SELECT * FROM users WHERE username = ?',
            [$username]
        );

        if ($user === null) {
            throw new InvalidCredentialsException('User not found');
        }

        // validate password
        // ...
    }
}
                    

Otázky

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

Riešenie (authentication)


class Authentication {
    private $userProvider;

    public function __construct(UserProviderInterface $userProvider) {
        $this->userProvider = $userProvider;
    }

    public function checkCredentials($username, $password) {
        $user = $this->userProvider->findUser($username);

        if ($user === null) {
            throw new InvalidCredentialsException('User not found');
        }

        // validate password
        // ...
    }
}
                    

Riešenie (UserProviderInterface)


interface UserProviderInterface {
    public function findUser($username);
}

class DoctrineDbalUserProvider implements UserProviderInterface {
    // ...
}

class TextFileUserProvider implements UserProviderInterface {
    // ...
}
                    

SOLID - Záver

Aby bol kód ľahko udržiavateľný, rozšíriteľ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

Ďakujem za pozornosť

Otázky?