preloader
WEB DEVELOPER

SOLID

Single responsibility:

Zasada pojedynczej odpowiedzialności – jest to zasada, która mówi o tym, że każda klasa powinna mieć wyłącznie jeden powód do zmiany. Klasa taka byłaby odpowiedzialna za jedną konkretną rzecz. Budowanie dużych klas może być problematyczne w momencie, kiedy będziemy chcieli wprowadzić jakieś zmiany.

PRZYKŁAD


<?php
	class Credit
	{		
		public function getRatings()
		{
			echo 'Oblicz odsetki';
		}
		
		public function changeCurrency()
		{
			echo 'Zamień walutę na PLN';
		}
	}
?>

W tym przykładzie klasa oblicza odsetki kredytu oraz umożliwia zamianę waluty. Łamie to zasadę jednej odpowiedzialności. Należy w taki wypadku wydzielić osobne klasy:

<?php
	class Credit
	{
		public function getRatings()
		{
			echo 'Oblicz odsetki';
		}
	}
	
	class Cantor
	{
		public function changeCurrency()
		{
			echo 'Zamień walutę na PLN';
		}
	}
?>

Po rozbiciu funkcji na dwie klasy każda z nich zajmuje się osobnym zadaniem. Dużo łatwiej jest prowadzić małe klasy, które odpowiadają za konkretne zadanie.

Open/closed:

Zasada otwarte/zamknięte – brzmi ona następująco: składniki oprogramowania (takie jak klasy, metody itp.) powinny być otwarte na rozszerzenie lecz zamknięte na modyfikacje. W zasadzie tej chodzi o to, aby mieć możliwość zmiany zachowania np. klasy, natomiast bez modyfikacji jej oryginalnego kodu.

PRZYKŁAD

<?php
	class Shape
	{
		public function printshape($shapetype)
		{
			if ($shapetype == 'square')
			{
				echo 'Rysuję kwadrat';
			} 
			
			else if ($shapetype == 'circle')
			{
				echo 'Rysuję okrąg';
			}
		}
	}
?>

Funkcja ta jest poprawna, jednak w przypadku, kiedy chcielibyśmy dołożyć nową figurę (np. trójkąt), nie dałoby się jej rozbudować w prosty sposób bez modyfikacji istniejącego kodu.
Pisząc tę funkcję możemy użyć np. interfejsu.

<?php
	interface Shape
	{
		public function printshape();
	}
	
	class Square implements Shape
	{
		public function printshape()
		{
			echo 'Rysuję kwadrat';
		}
	}
	
	class Circle implements Shape
	{
		public function printshape()
		{
			echo 'Rysuję okrąg';
		}
	}
	
	class Triangle implements Shape
	{
		public function printshape()
		{
			echo 'Rysuję trójkąt';
		}
	}
?>

Kod napisany w taki sposób jest przejrzysty, a na pewno też łatwy do rozszerzenia. Dodajemy kolejne kształty implementując funkcję z interfejsu. Nie musimy tutaj nic modyfikować, dzięki temu mamy pewność, że wszystkie funkcje, które zostały napisane wcześniej będą działały prawidłowo.

Liskov substitution:

Zasada podstawienia Liskov – mówi nam, że funkcje, które używają wskaźników lub referencji do klas bazowych, muszą być w stanie używać również obiektów klas dziedziczących po klasach bazowych, bez dokładnej znajomości tych obiektów. Stosowanie się do tej zasady pozwala na dostarczenie alternatywnej implementacji danej funkcjonalności bez zmiany Twojego kodu.

<?php
	class Animal
	{
		public function eat()
		{
			echo 'Je';
		}
		
		public function go()
		{
			echo 'Chodzi';
		}
		
		public function fly()
		{
			echo 'Lata';
		}
		
		public function fetch()
		{
			echo 'Aportuje';
		}
	}
	
	class Dog extends Animal
	{
		
	}
	
	class Bird extends Animal
	{
		
	}
	
	$animal = new Dog();
	$animal->eat();
	$animal->go();
	$animal->fly();
	$animal->fetch();
	
	$animal = new Bird();
	$animal->eat();
	$animal->go();
	$animal->fly();
	$animal->fetch();
?>

W tym przypadku mamy do czynienia ze źle przemyślanym mechanizmem dziedziczenia. Pies jest zwierzęciem, natomiast został obarczony implementacją metody fly(), która znajduje się w klasie bazowej. Pies może jeść, może chodzić, ale nie może latać. Odwrotnie np. ptak może latać, ale nie może aportować. Jest to złamanie zasady podstawienia Liskov.

<?php
	class Animal
	{
		public function eat()
		{
			echo 'Je';
		}
		
		public function go()
		{
			echo 'Chodzi';
		}
		
	}
	
	class FlyingAnimal extends Animal
	{
		public function fly()
		{
			echo 'Lata';
		}
	}
	
	class NonFlyingAnimal extends Animal
	{
		public function fetch()
		{
			echo 'Aportuje';
		}
	}
	
	class Dog extends NonFlyingAnimal
	{
		
	}
	
	class Bird extends FlyingAnimal
	{
		
	}
	
	$nonFlyingAnimal = new Dog();
	$nonFlyingAnimal->eat();
	$nonFlyingAnimal->go();
	$nonFlyingAnimal->fetch();
	
	$flyingAnimal = new Bird();
	$flyingAnimal->eat();
	$flyingAnimal->go();
	$flyingAnimal->fly();
?>

Po wprowadzeniu zmian zasada podstawienia Liskov działa prawidłowo. Tworząc obiekt Bird możemy użyć zarówno klasy FlyingAnimal jak i klasy z niej dziedziczącej Bird.

Interface segregation:

Zasada segregacji interfejsów – krótko mówiąc zawiera łatwy do zrozumienia przekaz: nie powinniśmy zmuszać klasy do implementacji metod, których ta klasa nie potrzebuje.
Powinniśmy unikać interfejsów, które zawierają dużą ilość metod, a jak wiadomo wszystkie metody zawarte w tym interfejsie muszą być zaimplementowane. Znacznie lepiej jest zdefiniować większą liczbę osobnych i lekkich interfejsów.

<?php
	interface Vehicle
	{
		public function startTheEngine();
		public function putOnHelmet();
		public function openTheDoor();
		public function move();
	}
	
	class Motorbike implements Vehicle
	{
		public function startTheEngine()
		{
			echo 'Uruchamiam silnik';
		}
		
		public function putOnHelmet()
		{
			echo 'Zakładam kask';
		}
		
		public function move()
		{
			echo 'Jadę';
		}
	}
	
	class Car implements Vehicle
	{
		public function startTheEngine()
		{
			echo 'Uruchamiam silnik';
		}
		
		public function openTheDoor()
		{
			echo 'Otwieram drzwi';
		}
		
		public function move()
		{
			echo 'Jadę';
		}
	}
?>

Uruchomienie aplikacji, w której znajdowałby się tak napisany kod z pewnością by się nie powiodło, dlatego, że klasa Motorbike i klasa Car musiałyby dodatkowo zaimplementować metody, które są im niepotrzebne. Podczas jazdy motocyklem nie musimy otwierać drzwi, natomiast podczas jazdy samochodem nie musimy zakładać kasku.
Aby wszystko działało prawidłowo należy podzielić interfejs Vehicle na kilka mniejszych.

<?php
	interface Vehicle
	{
		public function startTheEngine();
		public function move();
	}
	
	interface MotorbikeVehicle
	{
		public function putOnHelmet();
	}
	
	interface CarVehicle
	{
		public function openTheDoor();
	}
	
	class Motorbike implements Vehicle, MotorbikeVehicle
	{
		public function startTheEngine()
		{
			echo 'Uruchamiam silnik';
		}
		
		public function putOnHelmet()
		{
			echo 'Zakładam kask';
		}
		
		public function move()
		{
			echo 'Jadę';
		}
	}
	
	class Car implements Vehicle, CarVehicle
	{
		public function startTheEngine()
		{
			echo 'Uruchamiam silnik';
		}
		
		public function openTheDoor()
		{
			echo 'Otwieram drzwi';
		}
		
		public function move()
		{
			echo 'Jadę';
		}
	}
?>

W powyższym przykładzie w chwili obecnej mamy już wydzielone osobne funkcjonalności do osobnych interfejsów. Klasy implementują tylko te interfejsy, które są im potrzebne. Unikneliśmy wymuszania implementacji metod, które w danej klasie są niepotrzebne.

Dependency inversion:

Zasada odwrócenia zależności – czyli moduł wysokiego poziomu nie powinien zależeć od modułu niskiego poziomu. Obydwa powinny zależeć od abstrakcji. Zależność ta powinna być odwrócona poprzez wprowadzenie dodatkowych elementów.

<?php
	public function storeBike()
	{
		$bike = getBike();
		writeBike($bike);
	}
	 
	public function getBike()
	{
		echo 'Zwróć obiekt bike';
	}
	 
	public function writeBike(Bike $bike)
	{
		
	}
?>

Ponieważ akcja na wysokim poziomie zależy od dwóch akcji na niskim poziomie algorytm funkcji storeBike nie nadaje się do ponownego wykorzystania.
Trzeba przerobić ten algorytm na bardziej użyteczny.

<?php
	interface BikeReader
	{
		public function read()
		{
			echo 'Zwróć obiekt bike';
		}
	}
	 
	interface BikeWriter()
	{
		public function write(Bike $bike)
		{
			
		}
	}
	 
	public function storeBike(BikeReader $Reader, BikeWriter $Writer)
	{
		$bike = $Reader->read();
		$Writer->write($bike);
	}
?>

Nowy kod będzie działał lepiej, ponieważ funkcja przechowywania na wysokim poziomie zależy teraz tylko od abstrakcji.

Name*Email*WebsiteComment

Zostaw komentarz