L v SOLIDe
Milan Herda, 03 / 2018
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
- Liskov Substitution Principle
- Výhody LSP
- 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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/689274/images/7793136/solid-batman.jpg)
Liskov Substitution Principle
Liskovovej princíp zameniteľnosti
Liskov Substitution Principle
![](https://s3.amazonaws.com/media-p.slid.es/uploads/689274/images/7793190/lsp-duck.jpg)
Odvodená trieda musí byť náhradou svojej základnej triedy
Liskov Substitution Principle
![](https://s3.amazonaws.com/media-p.slid.es/uploads/689274/images/7213736/easy.jpg)
Byť dobrou náhradou svojej základnej triedy:
- poskytovať implementáciu pre všetky metódy
- mať rovnaké typy návratových hodnôt
- nesprísňovať požiadavky na argumenty
- nesprísňovať kontrakt
- neobchádzať kontrakt v kóde
Výhody dodržiavania
Liskov Substitution Principle
- časti kódu sú navzájom bezpečne vymeniteľné
- eliminácia chýb spôsobených nedodržaním kontraktu
![](https://s3.amazonaws.com/media-p.slid.es/uploads/689274/images/4495057/true-story.png)
Symptómy porušenia princípu
- nie sú poriadne implementované všetky metódy
- potomok má inú návratovú hodnotu ako rodič
- prísnejšie požiadavky na argumenty
- prísnejší kontrakt
- obchádzanie kontraktu v kóde
![](https://s3.amazonaws.com/media-p.slid.es/uploads/689274/images/7751792/code.jpg)
class AdminUser implements UserInterface {
private string $name;
private array $allowedSections = [];
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function setCapitalTown(
TownInterface $town
): self {
// ???
}
public function grantAccess(int $sectionId): bool
{
$this->allowedSections[] = $sectionId;
return true;
}
}
class Player implements UserInterface {
private string $name;
private TownInterface $capitalTown;
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function setCapitalTown(
TownInterface $town
): self {
$this->capitalTown = $town;
return $this;
}
public function grantAccess(int $sectionId): bool
{
// ???
}
}
interface UserInterface {
public function setName(string $name): self;
public function setCapitalTown(
TownInterface $town
): self;
public function grantAccess(int $sectionId): bool;
}
![](https://s3.amazonaws.com/media-p.slid.es/uploads/689274/images/7213734/bad-code.jpg)
Refactoring time!
Skúsime kód zrefaktorovať
![](https://s3.amazonaws.com/media-p.slid.es/uploads/689274/images/7751804/rewrite.jpg)
Stiahnite si zdrojáky
Problém: triedy majú nastaveného nesprávneho rodiča
Prečo má UserInterface metódy setCapitalTown a grantAccess, keď ich nevedia implementovať všetci potomkovia?
- rozdelíme interface
- každý typ používateľa bude dediť od správneho interfacu
Výsledok:
interface UserInterface {
public function setName(string $name): self;
}
interface PlayerInterface extends UserInterface {
public function setCapitalTown(TownInterface $town);
}
interface AdminUserInterface extends UserInterface {
public function grantAccess(int $sectionId);
}
class AdminUser implements AdminUserInterface {
public function setName(string $name) { /* ... */ }
public function grantAccess(int $sectionId) { /* ... */ }
}
class Player implements PlayerInterface {
public function setName(string $name) { /* ... */ }
public function setCapitalTown(TownInterface $town) { /* ... */ }
}
Príklad 2
interface BuildingsProviderInterface {
/**
* @return BuildingInterface[]
*/
public function getBuildings();
}
class BuildingsProvider implements BuildingsProviderInterface {
public function getBuildings() {
$buildings = [];
// ...
return $buildings;
}
}
class PremiumBuildingsProvider implements BuildingsProviderInterface {
public function getBuildings() {
$buildings = new BuildingCollection();
// ...
return $buildings;
}
}
class BuildingCollection implements Iterator { /* ... */}
![](https://s3.amazonaws.com/media-p.slid.es/uploads/689274/images/7213733/beyond.jpg)
<?php
use Example2\PremiumBuildingsProvider;
use Example2\BuildingsProvider;
use Example2\BuildingsProviderInterface;
require_once __DIR__ . '/vendor/autoload.php';
class UsageExample2
{
public function run(BuildingsProviderInterface $buildingsProvider)
{
$buildings = $buildingsProvider->getBuildings();
echo "pocet budov: " . count($buildings) . "\n";
foreach ($buildings as $building) {
echo $building->getName() ."\n";
}
}
}
$example = new UsageExample2();
$example->run(new BuildingsProvider());
$example->run(new PremiumBuildingsProvider());
![](https://media4.giphy.com/media/HhTXt43pk1I1W/giphy.gif)
Problém: rozdielny typ návratovej hodnoty, rodič ju špecifikuje nejasne
Keď by sme si dali návratovú hodnotu spočítať funkciou count, tak nám jedna implementácia zhavaruje.
- rodič musí mať jednoznačnejšiu špecifikáciu návratovej hodnoty
- upravíme potomkov, aby spĺňali špecifikáciu
interface BuildingsProviderInterface
{
public function getBuildings(): BuildingCollection;
}
// alebo
interface BuildingsProviderInterface
{
public function getBuildings(): array;
}
Výsledok:
Príklad 3
interface RangedStrengthCalculatorInterface
{
public function calculateStrength(
UnitInterface $unit,
int $howMany,
int $weatherType
): int;
}
class RangedStrengthCalculator implements RangedStrengthCalculatorInterface
{
public function calculateStrength(
UnitInterface $unit,
int $howMany,
int $weatherType
): int {
if (!($unit instanceof Archer) && !($unit instanceof LongBowArcher)) {
throw new InvalidArgumentException(
'Invalid unit type'
);
}
// ...
}
}
![](https://s3.amazonaws.com/media-p.slid.es/uploads/689274/images/7797787/bad-developer.jpg)
Problém: Prísnejšie požiadavky na argumenty, ako má rodič
Hoci trieda o sebe tvrdí, že akceptuje inštancie UnitInterface, tak v skutočnosti akceptuje iba Archer a LongBowArcher a inak zhavaruje.
- zistíme, čím je Archer a LongBowArcher výnimočný a rozdielny oproti UnitInterface
- upravíme/vytvoríme patričné interfacy a typehinty
- Archer a LongBowArcher sú logicky podtypom UnitInterface
- zavedieme nový interface RangedUnitInterface
- upravíme RangedStrengthCalculator aj RangedStrenghtCalculatorInterface
interface RangedStrengthCalculatorInterface
{
public function calculateStrength(
RangedUnitInterface $unit,
int $howMany,
int $weatherType
): int;
}
class RangedStrengthCalculator implements RangedStrengthCalculatorInterface
{
public function calculateStrength(
RangedUnitInterface $unit,
int $howMany,
int $weatherType
): int {
// už tu nie je žiadna kontrola typu inštancie
// ...
}
}
Príklad 4
interface MessageInterface
{
public function setText(string $text): self;
public function getText(): string;
}
class ArticleMessage implements MessageInterface
{
// ...
public function setText(string $text): self
{
// ...
}
public function getText(): string
{
//...
}
public function setArticleId(int $id)
{
// ...
}
}
class Article {
public function addMessage(MessageInterface $message)
{
$message->setArticleId($this->id);
// ...
}
}
Problém: Volaná metóda setArticleId nepatrí použitému interfacu.
Keď do addMessage vložíme inštanciu MessageInterface, ktorá nemá metódu setArticleId, tak kód zhavaruje
- programujeme len voči deklarovanému rozhraniu
- nedostatočné rozhranie vymeníme
Výsledok:
interface ArticleMessageInterface extends MessageInterface
{
public function setArticleId(int $id): self { /* ... */ }
}
class ArticleMessage implements ArticleMessageInterface
{
public function setText(string $text): self { /* ... */ }
public function getText(): string { /* ... */ }
public function setArticleId(int $id): self { /* ... */ }
}
class Article
{
public function addMessage(ArticleMessageInterface $message)
{
$message->setArticleId($this->id);
// ...
}
}
Opakovanie
Odvodená trieda musí byť náhradou svojej základnej triedy
Liskov Substitution Principle
Byť dobrou náhradou svojej základnej triedy:
- poskytovať implementáciu pre všetky metódy
- mať rovnaké typy návratových hodnôt
- nesprísňovať požiadavky na argumenty
- nesprísňovať kontrakt
- neobchádzať kontrakt v kóde
Výhody dodržiavania
Liskov Substitution Principle
- časti kódu sú navzájom bezpečne vymeniteľné
- eliminácia chýb spôsobených nedodržaním kontraktu
Viac informácií:
Matthias Noback - Principles of Package Design
![](https://s3.amazonaws.com/media-p.slid.es/uploads/689274/images/7071525/questions-ask-them-younso.jpg)
Ďakujem za pozornosť
L v SOLIDe
By Milan Herda
L v SOLIDe
- 548