Milan Herda, 03 / 2018
Profesia, FatChilli, Porada, BlueOrange, NOV
profesia.sk, domelia.sk, rtvs.sk, reality.sme.sk, living.sk...
Skratka predstavujúca 5 základných princípov dobrého softvérového návrhu
Odvodená trieda musí byť náhradou svojej základnej triedy
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;
}
Skúsime kód zrefaktorovať
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?
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) { /* ... */ }
}
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 { /* ... */}
<?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());
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.
interface BuildingsProviderInterface
{
public function getBuildings(): BuildingCollection;
}
// alebo
interface BuildingsProviderInterface
{
public function getBuildings(): array;
}
Výsledok:
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'
);
}
// ...
}
}
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.
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
// ...
}
}
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
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);
// ...
}
}
Odvodená trieda musí byť náhradou svojej základnej triedy
Viac informácií:
Matthias Noback - Principles of Package Design