SOLID

Piotr Woszczyk @ 2020

Czym jest SOLID?

  • Akronim
  • Zbiór zasad programowania obiektowego
  • Opracowany przez Roberta Martina w 2000 roku
  • Możliwy do użycia w większości współczesnych języków programowania
  • Niezależny od frameworka, środowiska, etc.

Single responsibility

  • Klasa / metoda powinna być odpowiedzialna za wykonanie tylko jednej czynności
  • Nie powinien istnieć więcej jak jeden powód do zmiany klasy / metody
  • Zyskujemy czytelniejszy kod oraz łatwiejszy refactoring
<?php

class Person {
    public $name;

    public function render() {
    	echo '<p>'.$this->name.'</p>';
    }
    
    public function validate() : bool {
    	return null == $this->name;
    }
}

Przykład negatywny

<?php

class Person {
    public $name;
}

class Validator {
    protected $person;

    public function __construct(Person $person) {
    	$this->person = $person;
    }
    
    public function validate() : bool {
    	return null == $this->person->name;
    }
}

class Template {
    protected $person;
    
    public function __construct(Person $person) {
    	$this->person = $person;
    }
    
    public function render() {
    	echo '<p>'.$this->person->name.'</p>';
    }
}

Przykład pozytywny

Open–closed

  • Kod powinien być gotowy na rozszerzenie i zamknięty na modyfikacje
  • Nowe funkcjonalności powinny być możliwe do dodania bez konieczności zmiany istniejącego kodu
  • Zyskujemy wyższą kompatybilność wsteczną i mniejsze ryzyko uszkodzenia dotychczasowego kodu
<?php

class File {
    public $extension;
}

class Importer {
    public function execute($file) {
    	if ($file->extension == 'xls') {
            $this->importXls($file);
        } else if ($file->extension == 'xml') {
            $this->importXml($file);
        }
    }
}

Przykład negatywny

<?php

class File {
    public $extension;
}

interface Formater() {
    public function supports(string $format) : bool;
    
    public function format(File $file) : array;
}

class Importer {
    protected $formaters;

    public function registerFormaters(FormaterInterface $formater) {
    	$this->formaters[] = $formater;
    }
	
    public function execute($file) {
    	foreach ($this->formaters as $formater) {
            if ($formater->supports($file->extension)) {
                $data = $formeater->format($file);
                break;
            }
        }
        // ...
    }
}

Przykład pozytywny

Liskov substitution

  • Klasy potomne powinny rozszerzać możliwości klasy rodzica zachowując działanie klasy rodzica
  • Zyskujemy brak niespodziewanych zachowań w trakcie wykonywania kodu
?<php

class CRUD {
    public function create() {}
    
    public function read() {}
    
    public function update() {}
    
    public function delete() {}
}

class ReadonlyUserCRUD extends CRUD {
    public function create() {
    	throw new Exception();
    }
    
    public function update() {
    	throw new Exception()
    }
    
    public function delete() {
    	throw new Exception()
    }
}

Przykład negatywny

?<php

class Read {   
    public function execute() {}
}

class ReadonlyUserCRUD extends Read {
    protected $logger;

    public function execute() {
    	$this->logger->debug('ReadonlyUserCRUD::execute');
    	return parent::execute();
    }
}

Przykład pozytywny

Interface segregation

  • Interfejsy powinny definiować możliwie mały zestaw metod
  • Nie należy zbytnio uogólniać interfejsów
  • Zyskujemy łatwiejsze implementowanie interfejsów oraz nie powstaje nadmiarowy kod
<?php

interface Exportable {
    public function toPDF() {};
    public function toHTML() {};    
}

class ReportExport implements Exportable {
    public function toPDF() {}
    public function toHTML() {}
}

class ImageExport implements Exportable {
    public function toPDF() {
    	return;
    }
    
    public function toHTML() {}
}

Przykład negatywny

<?php

interface ExportableToPDF {
    public function toPDF() {};
}

interface ExportableToHTML {
    public function toHTML() {};    
}

class ReportExport implements ExportableToPDF, ExportableToHTML {
    public function toPDF() {}
    public function toHTML() {}
}

class ImageExport implements ExportableToHTML {
    public function toHTML() {}
}

Przykład pozytywny

Dependency inversion

  • Klasy wysokiego poziomu (np. realizujące logikę biznesową) nie powinny być zależne od klas niskiego poziomu (np. dostarczających dane)
  • Zależności między klasami powinny być definiowane przez dodatkową warstwę abstrakcji (zazwyczaj interfejsy)
  • Zyskujemy zmniejszenie siły zależności między klasami oraz możliwość późniejszej ich wymiany

Przykład negatywny

<?php

class Parser {
    public function getHeaders() {}
}

class Importer {
    public function execute() {
    	$parser = new Parser();
        $headers = $parser->getHeaders();
        // ...
    }
}
<?php

interface RequestInterface() {
	public function getHeaders();
}

class Parser implements RequestInterface {
    public function getHeaders() {}
}

class Importer {
	protected $parser;

	public function __construct(RequestInterface $parser) {
    	$this->parser = $parser;
    }

    public function execute() {    	
        $headers = $this->parser->getHeaders();
        // ...
    }
}

Przykład pozytywny

SOLID

By Piotr Woszczyk

SOLID

  • 48