Jeśli tutaj trafiłeś pewnie zastanawiałeś się jak zostać lepszym programistą. Jeśli słyszałeś kiedyś o SOLID to prawdopodobnie czytałeś książkę Clean Code – Martin Robert C. Jeśli tego jeszcze nie zrobiłeś, to polecam Ci przeczytanie jej. Czym jest SOLID?

SOLID to zbiór reguł, które zostały zdefiniowane przez programistów. Reguły te mają na celu ułatwienie pisania kodu, który jest czysty, łatwy w utrzymaniu i rozwijaniu. Stosując się do reguł SOLID, zauważysz znaczną różnicę w jakości kodu, który piszesz.

Single responsibility principle(SRP), czyli zasada jednej odpowiedzialności klasy

Zasada ta mówi o tym, że każda klasa lub moduł powinien być odpowiedzialny za dokładnie jedną funkcjonalność oraz że nie powinien istnieć więcej niż jeden powód do zmiany kodu klasy/modułu. Reguła ta została ustanowiona przez Roberta C. Martina w książce  „Agile Software Development, Principles, Patterns, and Practices” . Jak to wygląda w praktyce? Zobaczmy na kod:

<?php

namespace Entity;

class User
{
   private $email;
   private $name;
	
   public function setName($name)
   {
      $this->name = $name
   }
	
   public function getName()
   {
      return $this->name;
   }
	
   public function setEmail($email)
   {
	   if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
	     list($login, $domain) = explode("@", $email);
		 if (false === checkdnsrr($domain)) {
			 throw new \Exception("Mail does not exists");
		 }
	   } else {
	         throw new \Exception("Mailis in invalid format");
	   }
	   
	   $this->email = $email;
   }
	
   public function asJSON()
   {
      return json_encode([
	      'email' => $this->email,
		  'name'  => $this->name,
 	  ]);
   }
}

?>

 

Na pierwszy rzut oka mamy prostą klasę obsługującą użytkownika. Klasa ustawia imię oraz email oraz jeśli programista chce dostać json-a wywołuję metodę „asJson”. Jakie są odpowiedzialności klasy?

  • ustawienie zmiennych dotyczących użytkownika imię, email
  • rozszerzona walidacja e-maila
  • zapisanie użytkownika w formacie JSON.

 

Nadal wszystko wydaję się ok. Załóżmy teraz, że chcemy rozszerzyć klasę bo przyszło wymaganie biznesowe by dodać format xml, yaml, one-line itp. Więc dodajemy kolejne metody asXML(), asYAML, asOneLine, do tego doszły kolejne pola, gdzie musimy napisać kolejne walidatory. Klasa robi się coraz większa. Następnie mamy kolejną klasę, która wykorzystuje walidację e-mail, którą mamy już w użytkowniku. Co robi programista jeśli ma to zrobić szybko? Ctrl+c, ctrl + v. Następnie mamy kilka klas, które wykorzystują walidację, zmianę formatu i wszędzie skopiowany kod, ale zmienia się format XML więc w każdej tej klasie musimy zmienić dokładnie to samo.

Problem powstał już na samym początku i eskalował do większych rozmiarów. Kolejni programiści, którzy pracują przy kodzie dokładają swoje „cegiełki” i z klasy, która miałą 30 linii robi się tz. „God Class” mający 3 000 lini. Wydaje Ci się to niemożliwe? Uwierz mi, że widziałem większe klasy w projektach. Rzeczy, które pisałem dotyczą też innych zasad SOLID, ale wszystkie regułyłączą się ze sobą, gdyż mają na celu uzyskanie kodu wysokiej jakości. Zobaczmy jak ta klasa, mogłaby wyglądać.

<?php

class User implements FormatableInterface
{
   private $email;
   private $name;
   private $emailValidator;


   public function __construct(ValidatorInterface $emailValidator)
   {
      $this->emailValidator = $emailValidator;
   }
	
   public function setName($name)
   {
      $this->name = $name
   }
	
   public function getName()
   {
      return $this->name;
   }
	
   public function setEmail($email)
   {
	   if (!$emailValidator->isValid($email)) {
	      throw new \Exception("invalid email");
	   }
	   
	   $this->email = $email;
   }
	
   public function getFormatedProperties()
   {
      return [
	      'email' => $this->email,
		  'name'  => $this->name,
 	  ];
   }
}


interface ValidatorInterface
{
    public function isValid($variable);
}


class EmailValidator implements ValidatorInterface
{
	public function isValid($variable)
	{
	   if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
			 list($login, $domain) = explode("@", $email);
			 if (false === checkdnsrr($domain)) {
				 return false
			 }
		   } else {
				return false;
	   }
	
		return true;
	}
}

interface FormatableInterface
{
   public function getFormatedProperties();
}


interface FormatterInterface
{
    public function toFormat(FormatableInterface $object);
}


class JsonFormatter implements FormatterInterface
{
	
   public function toFormat(FormatableInterface $object)
   {
	   $result = [];
	   
       foreach ($object->getFormatedProperties() as $property) {
		   $result[$property] = $object->{'get'.ucfirst($property)};
	   }
	   
	   return json_encode($result);
   }
}

class XMLFormatter implements FormatterInterface
{
	
   public function toFormat(FormatableInterface $object)
   {
	  $xml = new SimpleXMLElement('<root/>');
	   
      array_walk_recursive($test_array, array ($xml, 'addChild'));
      return  $xml->asXML();
   }
}

// użycie


$user = new User();
$user->setName("Robert");
$user->setEmail('robert@gmail.com')

$xmlFormatter = new XMLFormatter();
$jsonFormatter = new JsonFormatter();

$userInXml = $xmlFormatter->toFormat($user); 
$userInJson = $jsonFormatter->toFormat($user);

 

Jak widzisz podzielenie odpowiedzialności na inne klasy sprawiło, iż ten sam kod może być użyty w wielu komponentach. Rozwiązanie, które zastosowałem nie jest idealne, bo istnieją lepsze metody na osiągnięcie efektu(np połączenie walidatorów i walidacja poza encją), jednakże nie chciałem komplikować za bardzo kodu. Mam nadzieję, że widzisz w tym sens i zdołałem Cię przekonać do takiego podejścia do OOP. W kolejnych wpisach postaram się opisać inne zasady SOLID-nego programowania. Jeśli masz pytania zapraszam do komentowania.