Witaj, Gościu O nas | Kontakt | Mapa
Wortal Forum PHPEdia.pl Planeta Kubek IRC Przetestuj się!
Wyszukiwarka

Wyszukiwarka

Aby odnaleźć interesujące Cię informacje wpisz poniżej szukane frazy:
Logowanie

Logowanie

Zaloguj się aby zobaczyć swój profil:

Refaktoryzacja kodu i wzorce projektowe

  • dokumentowania,
  • czytelności,
  • i przede wszystkim utrzymywania kodu.

Budowniczy (Factory)

Jak pisałem we wstępie wzorzec projektowy to odpowiedź na problem. W takim razie jaki problem rozwiązuje ten wzorzec? Kod naszej aplikacji często zależy od instancji obiektów kilku klas, każdą z nich tworzymy z wykorzystaniem słowa new. Wyobraźmy sobie przykład.

<?php
$log = new FileLog();
$log->doLog("Aplikacja uruchomiona"); // Dodaje do logów tekstowych nową informację
?>

Wykorzystujemy klasę logowania FileLog, która pomaga nam śledzić proces działania naszej aplikacji. Nie będę zagłębiał się w implementację techniki zapisywania, to nie stanowi istoty naszego problemu.Postawmy sobie pytanie: co jeśli kiedyś będziemy chcieli zmienić typ klasy, która ma być tworzona? Co jeśli nagle stwierdzimy, że odpowiednią metodą zapisywania logów będzie umieszczanie ich w bazie danych? Wywołań instancji FileLog jest prawdopodobnie w naszym kodzie całe mnóstwo - nasza aplikacja na sztywno związała fakt logowania i sposób wykonywania tej czynności. Naprawmy błąd używając naszego wzorca.

<?php
$log = Log::load(); // Mmetoda zwróci instancję, ale jakiej klasy? Jak została stworzona?
$log->doLog(&#8222;Aplikacja uruchomiona&#8221;);
?>

Zamknęliśmy sposób tworzenia obiektu w metodzie load nowej klasy Log. Zwraca instancję, którą będziemy traktować identycznie jak wcześniejszy FileLog. Wykorzystując ten wzorzec nie interesuje nas jak została stworzona instancja naszego obiektu logującego. Nie wiemy przede wszystkim z jakiej konkretnie klasy będziemy ostatecznie korzystać, czyli w praktyce w jaki sposób logi będą zapisywane. Musimy jednak zapobiec konfliktom. Zunifikujmy informacje czym jest klasa logowania, co wykonuje - jak dostać się do odpowiedniej funkcjonalności. Ustalmy jak ma wyglądać niezależnie od implementacji. Stwórzmy odpowiedni interfejs:

<?php
interface ILog
{
	public function doLog($message);
}
?>

W interfejsie opisujemy tylko jedną metodę wspólną dla koncepcji klas logowania &#8211; zapisującą podaną wiadomość. Poinformujmy naszą aplikację, że klasa DbLog implementuje interfejs ILog.

<?php
class DbLog implements Ilog
{
	public function doLog($message)
	{
		// Tu dodajemy informację do bazy danych
	}
}
?>

Świetnie! Jeszcze tylko ostatnia klasa Log, z naszym "budowniczym".

<?php
class Log
{
	static public function load()
	{
		return new DbLog();
	}
}
?>

Strategia (Strategy)

Idąc dalej tropem rozdzielania elementów i modularyzacji spójrzmy na wzorzec "strategii". Wykorzystując go wyłączamy algorytmy, umieszczając je w odpowiednich klasach zewnętrznych. Klarownym przykładem jest walidacja danych, np. przy rejestracji użytkownika:

<?php
if( isset($_POST['submit']) ) {
	if( !isset($_POST['username']) ) {
		echo 'Podaj nazwe uzytkownika';
	} else if( strlen($_POST['username']) <= 5 ) {
		echo 'Nazwa użytkownika musi mieć przynajmniej 5 znaków';
	} else if( /* kolejny warunek itd. */ ) {
		echo 'Komunikujemy kolejny bląd';
	} else {
		zapisz_uzytkownika($_POST['username']);
		echo 'Zostaleś zapisany!';
	}
}
?>

Pełno warunków, logika sprawdzania zmiennej wymieszana z wyświetlaniem komunikatów, na sztywno wpisane warunki - nie jest najlepiej. Zauważmy, że każdy test zmiennej w powyższych instrukcjach warunkowych, czy to sprawdzenie jej istnienia czy długości, jest pewnym algorytmem walidacji. Cały proces jest po prostu wywołaniem kolejnych, różnych, algorytmów (strategii) i przetestowanie zmiennej pod ich kątem. Możemy zmodyfikować powyższy kod by był bardziej czytelny i by zamknąć specyficzne warunki walidacji do późniejszego wykorzystania w innych miejscach aplikacji. Najpierw, klasycznie już budujemy interfejs, tym razem walidatora:

<?php
interface IValidate
{	
	public function __construct(&$var);
	public function isValid();
	public function getErrors();
}
?>

W następnym kroku tworzymy sobie klasy implementujące powyższy interfejs, dzięki czemu każda będzie osobnym algorytmem walidacji. Metoda isValid() takiej klasy zwróci nam czy zmienna $var poprawnie przeszła walidacje, a metoda getErrors() przekaże nam informacje o błędach. Przykład?

<?php
class UsernameLengthValidate implements Ivalidate
{
	private $_var;
	private $_errors = array();
	
	public function __construct( &$var )
	{
		$this->_var = &var;
	}
	
	public function isValid()
	{
		if( strlen($var) <= 5 ) {
			$this->_errors[] = 'Nazwa musi mieć przynajmniej 5 znaków';
			return false;
		}
		return true;
	}

	public function getErrors()
	{
		return $this->_errors;
	}
}

Powinniśmy zastosować tu także klasę abstrakcyjną, jednak nie chcę rozpraszać, zostańmy więc przy meritum tematu. Kod jak dla tak prostej operacji, wydaje się niebagatelnie dłuższy niż wcześniej, ale zauważmy, że walidacja wygląda już o niebo lepiej i pozwala w prosty sposób dodawać kolejne reguły:

<?php
if( isset($_POST['submit']) ) {
	$username = &$_POST['username'];
	// tworzymy listę walidatorów przekazując zmienną
	$list = array();
	$list[] = new ExistsValidate($username);
	$list[] = new UsernameLengthValidate($username);
	// uruchamiamy każdy
	$errors = array();
	foreach( $list as $validate ) {
		if( !$validate->isValid() ) {
			$errors = array_merge($errors,$validate->getErrors());
		}
	}
	// jeśli wystąpiły błędy wyświetlamy je lub uruchamiamy docelową akcję
	if( count($errors) ) {
		echo 'Wystąpiły błędy:';
		echo '<ul>';
		foreach( $errors as $error ) {
			echo '<li>'.$error.'</li>';
		}
		echo '</ul>';
	} else {
		zapisz_uzytkownika($username);
		echo 'Zostaleś zapisany!';    
	}
}

Wstrzykiwanie zależności i kontener zależności (Dependency Injection, DI Container)

Czas teraz dodać naszym przypadkom trochę pikanterii. Wymiana komponentów bibliotek zawsze była kłopotliwa, a teraz weźmy pod uwagę zaprojektowanie architektury typu plug-in. Mając na uwadze otwieranie aplikacji na takie właściwości musimy myśleć jeszcze szerzej niż przy wcześniejszych przykładach. Oczywiście, wzorzec "budowniczego" oddaje nam możliwość "wstrzyknięcia" do kodu takiej klasy i takiego obiektu który sobie ręcznie wybierzemy, ale pójdźmy krok dalej. Najpierw szybki przykład.

<?php
$mail = new Mail;
$mail->setRecipient('ceo@google.com');
$mail->setBody('Hej! Mam dla Ciebie propozycję nie do odrzucenia..');
$mail->send();
?>

Proste wykorzystanie pewnej klasy Mail, powtarzające się prawdopodobnie w wielu miejscach aplikacji. Było by świetnie, gdyby możliwość podmiany klasy była łatwa, może nawet z poziomu pliku konfiguracyjnego? Moglibyśmy w łatwy sposób zmieniać sposób wysyłania maili na taki wykorzystujący autoryzację SMTP lub z użyciem zewnętrznej biblioteki.Generalnie chcemy w każdej chwili działania aplikacji mieć możliwość wymiany najważniejszych elementów. Stwórzmy dla naszej aplikacji "kontener zależności", jeden element, "uniwersalnego budowniczego", który będzie decydował jakie implementacje i jakiej klasy wstrzykiwać do naszej aplikacji.By dynamicznie stworzyć instancje klasy możemy użyjemy refleksji, czyli mechanizmów odwrotnej inżynierii wbudowanych w PHP.

<?php
$className = 'Mail'; // definiujemy nazwę klasy
$reflection = new ReflectionClass($className); // tworzymy obiekt refleksji klasy
$mail = $reflection->newInstance(); // tworzymy instancję
?>

To wystarczy by tworzyć obiekty, których nazwa klasy nie jest znana w czasie projektowania, nie jest zapisana w kodzie ręcznie. Muszę nadmienić, że nie pozwolimy sobie teraz na stworzenie rozbudowanej implementacji "kontenera wstrzykiwania zależności", to już temat na inny artykuł. Zobaczmy jednak jak wyglądało by stworzenie bardzo prostego kontenera.

<?php
class DIContainer
{
	protected $_storage = array();

	public function setClass($alias,$class)
	{
		$this->_storage[$alias] = $class;
	}

	public function getInstance($alias)
	{
		$className = $this->_storage[$alias];
		$reflection = new ReflectionClass($className);
		return $reflection->newInstance();		
	}
}
?>

Wykorzystując metodę setClass() zapisujemy nazwę klasy i nazwę-alias używaną w aplikacji. Z kolei getInstance() zwraca instancję klasy danego aliasu. Skomplikowane? Przykład rozwieje wszelkie wątpliwości.

<?php
$di = new DIContainer;
$di->setClass('Mail','SmtpMail');
$di->setClass('Db','MysqlDb');
// ...trochę kodu dalej
$mail = $di->getInstance('Mail');
$mail->setRecipient('ceo@google.com');
$mail->setBody('Hej! Mam dla Ciebie propozycję nie do odrzucenia..');
$mail->send();
?>

Słowo końcowe

Informacje na podobny temat:
Wasze opinie
Wszystkie opinie uzytkowników: (5)
Literówki
Sobota 07 Listopad 2009 7:46:34 pm - tua1 <tua1_at_interia.pl>

Najpoważniejsza literówka, dziwne, że nikt tego do tej pory nie wytknął:

Factory to w tłumaczeniu wzorzec projektowy Fabryka, a nie Builder (Budowniczy). Builder z tego co pamiętam służy do określenie sposobu tworzenia obiekty jednego typu. Proszę o poprawienie jeśli jest inaczej

Fabryka to wzorzec do tworzenia obiektu z całej rodziny klas, dzieli się na statyczną fabrykę i na abstrakcyjną implementacja fabryki abstrakcyjnej może wyglądać np. tak:

class ConcreteLoggerFactory implements LoggerFactory {


public Logger getLogger($loggerName) {
switch($loggerName) {

case 'FileLog': return new FileLogger()
//itd
}
}
}

Literówki
Środa 26 Sierpień 2009 4:35:50 pm - skowron-line

interfajse masz IValidate
ale dziedziczysz już
class UsernameLengthValidate implements Ivalidate.

Pisany na kolanie?
Poniedziałek 17 Sierpień 2009 10:20:26 am - tuner <turneliusz_at_gmail.com>

@cojack, mam nadzieję, że zwięźle napisany tekst pokazujący krótkie i życiowe zastosowanie paru wzorców będzie ciekawą lekturą oraz zachęci do dalszych poszukiwań. Niestety w sieci jest już wiele wywodów akademickich mających mało wspólnego z realnym wykorzystywaniem takich dobrych praktyk. Jeżeli jesteś jednak zainteresowany pisaniem dokładnych opracowań poszczególnych wzorców, na pewno znajdą oddanych czytelników. Pamiętaj tylko by nie stworzyć kolejnej kopii nudnych podręczników. Pozdrawiam!

Troche smutno
Niedziela 26 Lipiec 2009 8:46:32 am - cojack

Sory ale Ty to na kolanie pisałeś podczas przerwy w szkole? Bo ja nie wiem, raptem dwa akapity a temat rzeka. Zresztą opisane wzorce są ciekawe, Twój sposób ich przedstawienia jest bagatelizujący sprawę. o której sam wcześniej napisałeś by jej nie bagatelizować. To ja nie wiem o czym my rozmawiamy. Na moim blogu w ciągu miesiąca znajdzie się obszerny opis paru wzorców obiektowych.

Błędy w listingach
Piątek 10 Kwiecień 2009 8:11:38 pm - bigzbig <heintze_at_o2.pl>

Bardzo fajny artykuł i chętnie przeczytałbym o innych wzorcach projektowych. Jedyną uwagę jaką mam to błędy w listingach. Np. w listingu zaczynającym się od "class UsernameLengthValidate" znalazłem trzy błędy. Prosiłbym o większą staranność

Mentax.pl    NQ.pl- serwery z dodatkiem świętego spokoju...   
O nas | Kontakt | Mapa serwisu
Copyright (c) 2003-2024 php.pl    Wszystkie prawa zastrzeżone    Powered by eZ publish Content Management System eZ publish Content Management System