Dizajn paterni (Design patterns)

Postoje brojni pristupi i različiti načini za izgradnju kôda vaše web aplikacije, samo je pitanje koliko ćete vremena posvetiti arhitekturi vašeg sistema. Obično je dobra ideja da se vodite nekim uobičajenim šablonima i praksama, zato što će na taj način vaš kôd biti jednostavniji za održavanje i lakši za razumevanje.

Factory

Jedan od najčešće korišćenih dizajn paterna je Factory. U njegovom slučaju, sve što neka klasa radi jeste kreiranje objekta kojeg nameravate da koristite. Pogledajmo sledeći primer Factory paterna:

<?php
class Automobile
{
    private $vehicleMake;
    private $vehicleModel;

    public function __construct($make, $model)
    {
        $this->vehicleMake = $make;
        $this->vehicleModel = $model;
    }

    public function getMakeAndModel()
    {
        return $this->vehicleMake . ' ' . $this->vehicleModel;
    }
}

class AutomobileFactory
{
    public static function create($make, $model)
    {
        return new Automobile($make, $model);
    }
}

// Factory kreira Automobile objekat
$veyron = AutomobileFactory::create('Bugatti', 'Veyron');

print_r($veyron->getMakeAndModel()); //ispisuje "Bugatti Veyron"

Ovaj kôd koristi Factory za kreiranje instance Automobile klase. Postoje dva moguća benefita pisanja kôda na ovaj način. Prvi je to što ćete u slučaju izmene, preimenovanja ili zamene Automobile klase, morati da menjate jedino kôd u njenom factory-u, umesto na svakom mestu gde se koristi klasa Automobile. Druga prednost je ta što u slučaju da je sâmo kreiranje objekta kompleksna operacija, sav taj posao možete obaviti u factory klasi, umesto da ga ponavljate svaki put kada vam treba nova instanca.

Korišćenje Factory paterna nije uvek neophodno (ispravno). Prethodni primer je toliko prost da bi korišćenje factory-a bio samo nepotrebna komplikacija. Ipak, ako radite na velikom i kompleksnom projektu, korišćenje factory-a vas možete lišiti dosta problema.

Singleton

Pri razvoju web aplikacija, često ima smisla konceptualno i arhitekturalno omogućiti korišćenje samo jedne instance određene klase. Singleton patern omogućava upravo tako nešto.

<?php
class Singleton
{
    /**
     * @var Singleton Referenca *Singleton* instance ove klase.
     */
    private static $instance;

    /**
     * Vraća *Singleton* instancu ove klase.
     *
     * @return Singleton The *Singleton* instance.
     */
    public static function getInstance()
    {
        if (null === static::$instance) {
            static::$instance = new static();
        }

        return static::$instance;
    }

    /**
     * Konstruktor je protected kako bi izvan klase bilo onemogućeno
     * kreiranje *Singleton* instance preko `new` operatora.
     */
    protected function __construct()
    {
    }

    /**
     * Sprečavanje kloniranja *Singleton* instance.
     *
     * @return void
     */
    private function __clone()
    {
    }

    /**
     * Sprečavanje deserijalizacije *Singleton* instance.
     *
     * @return void
     */
    private function __wakeup()
    {
    }
}

class SingletonChild extends Singleton
{
}

$obj = Singleton::getInstance();
var_dump($obj === Singleton::getInstance());             // bool(true)

$anotherObj = SingletonChild::getInstance();
var_dump($anotherObj === Singleton::getInstance());      // bool(false)

var_dump($anotherObj === SingletonChild::getInstance()); // bool(true)

Ovaj kôd implementira singleton patern koristeći statičku promenljivu i statički getInstance() metod. Obratite pažnju i na sledeće:

Signleton patern je koristan u situacijama kada treba da obezbedimo da imamo samo jednu instancu neke klase u toku jednog kompletnog ciklusa u aplikaciji. Tipičan primer su globalni objekti (kao što je neka Configuration klasa) ili deljeni resursi (kao što je event queue).

Treba da budete oprezni pri korišćenju Singleton paterna, jer sama njegova priroda uvodi globalno stanje u vašu aplikaciju, čime se smanjuje njena testabilnost. U većini slučajeva, dependency injection princip može (i treba) da se koristi umesto Singleton klasa. Korišćenjem dependency injection-a ne uvodimo nepotrebne direktne zavisnosti u dizajn naše aplikacije, jer objekat koji bude koristio taj neki deljeni ili globalni resurs neće imati znanja o tome o kojoj se tačno klasi radi.

Strategy

Primenom Strategy paterna enkapsulirate grupu određenih algoritama, pri čemu je klijentska klasa odgovorna za instanciranje konkretnog algoritma, bez znanja o načinu na koji je on implementiran. Postoji nekoliko varijacija ovog paterna, a najjednostavniji od njih će biti demonstriran u nastavku.

Prvi deo prikazuje grupu algoritama za ispis nekog niza podataka: jedan vrši nativnu, drugi radi JSON serijalizaciju, a treći ga ostavlja netaknutim:

<?php

interface OutputInterface
{
    public function load();
}

class SerializedArrayOutput implements OutputInterface
{
    public function load()
    {
        return serialize($arrayOfData);
    }
}

class JsonStringOutput implements OutputInterface
{
    public function load()
    {
        return json_encode($arrayOfData);
    }
}

class ArrayOutput implements OutputInterface
{
    public function load()
    {
        return $arrayOfData;
    }
}

Enkapsuliranjem ovih algoritama u zasebne klase, na elegantan i čist način stavljate do znanja drugim programerima da lako mogu da dodaju novu output strategiju, bez uticaja na klijenstki kôd.

Primetićete da svaka ‘output’ klasa implementira određeni OutputInterface. Ovaj interfejs ima za cilj ima da definiše jednostavan “ugovor” koji svaka nova implementacija mora da ispoštuje. Takođe, implementiranjem jednog zajedničkog interfejsa, kao što ćete i videti u nastavku, biće omogućena primena Type Hinting-a, kako bi se obezbedilo to da kôd koji koristi ovu funkcionalnost radi sa ispravnim tipovima klasa, u ovom slučaju ‘OutputInterface’ implementacijama.

Sledeći primer kôda demonstrira kako poziv klijentske klase može da koristi neki od ovih algoritama tako što će ga zahtevati prilikom izvršavanja:

<?php
class SomeClient
{
    private $output;

    public function setOutput(OutputInterface $outputType)
    {
        $this->output = $outputType;
    }

    public function loadOutput()
    {
        return $this->output->load();
    }
}

Ova klijentska klasa ima privatno svojstvo koje mora da bude prosleđeno prilikom instanciranja, pri čemu to mora da bude ‘OutputInterface’ implementacija. Nakon što je ovo svojstvo postavljeno, poziv loadOutput() metoda će pozvati load() metod neke konkretne Output klase.

<?php
$client = new SomeClient();

// Želite niz?
$client->setOutput(new ArrayOutput());
$data = $client->loadOutput();

// Želite JSON?
$client->setOutput(new JsonStringOutput());
$data = $client->loadOutput();

Front Controller

U slučaju Front Controller paterna postoji jedinstvena ulazna tačka u vašoj aplikaciji (npr. index.php) koja prihvata sve zahteve. Taj deo kôda je odgovoran za učitavanje svih zavisnosti, procesiranje samog zahteva i slanja odgovora browser-u. Ovaj patern može biti veoma koristan iz razloga što pospešuje modularan kôd i obezbeđuje cetralno mesto za ubacivanje nekog kôda koji treba da se izvršava na svaki zahtev (kao na primer saniranje ulaznih podataka).

Model-View-Controller

Model-View-Controller (MVC) patern i srodni paterni kao što su HMVC i MVVM vam omogućavaju da podelite kôd na logičke objekte pri čemu svaki ima posebnu namenu. Model predstavlja sloj business logike i najčeće obavlja manipulaciju podacima (dohvatanje, čuvanje) koji se koriste u okviru aplikacije. Kontroleri (Controllers) prihvataju zahtev (request), uzimaju i obrađuju podatke od modela, a zatim učitavaju View-ove kako bi poslali odgovor (response). View-ovi su templejti (markup, xml i slično) za prikaz podataka čiji se rezultujući output šalje browser-u u okviru samog odgovora.

MVC je najčešće korišćeni arhitekturalni patern u popularnim PHP frejmvorcima.

Naučite više o MVC-u i srodnim paternima: