Tacsiazuma
Tacsiazuma A letscode.hu alapitója, több, mint egy évtized fejlesztői tapasztalattal. Neovim függő hobbi pilóta.

OOP’s I DI’d it again!, avagy dependency injection praktikák

49734Mielőtt bármibe is belekezdenénk, először is tisztáznunk kell mi is az a dependency injection ( függőség injektálás, magyarul elég morbidul hangzik). Az objektumorientált programozásban az osztályaink egymással közreműködnek és legtöbb osztályunk (nem mind, hiszen az lehetetlen) explicit módon igényli egy másik használatát. Ha használtunk már pl. PDO-t egy osztályunkban, akkor is pont ezt tettük. Anélkül az osztály nélkül a miénk sehogyse működne, függ tőle (depends on). Ha még most sem világos, akkor vegyünk egy konkrét példát.

A programozó, mint osztály

1
2
3
4
5
6
class Programozo {
    public function __construct(KaveAutomata $kaveautomata, Eclipse $ide) {
          $kaveautomata->rugdos()->iszik();
          $ide->ujraInditMertMarBelassult();
    }
}

A fenti példa kicsit unortodoxnak tűnhet, de mindjárt elmagyarázom. A dependency injection egyik legegyszerűbb módja az, hogyha a konstruktoron át, a meghíváskor adjuk át a szükséges osztályok példányait (constructor injection). A programozónak ugye szüksége van egy fejlesztői környezetre, amin dolgozik, valamint esetünkben kávéra, ahhoz hogy működni tudjon. Ha ezek nincsenek meg, akkor bizony nem tudjuk munkára fogni az illetőt. A fenti példában explicit módon kikötöttük, hogy automatás kávét iszunk és Eclipse-t használunk. Ez szép és jó, ellenben a kódunkat (és vele együtt a programozót is) bebetonoztuk a kódunkba. Ugyanis mi van akkor, ha elmegyünk egy másik helyre, ahol kotyogós kávéfőző van, netán PhpStorm vagy épp NetBeans? Nos a mi programozónk ezt nem fogja elfogadni és hibaüzenettel elszáll, mert neki bizony elvei vannak.

Megoldás 0.2.13-rev42342

Mégis mit tehetnénk az ügy érdekében? Ha nem kötjük ki explicit módon, hogy milyen osztályokat igényel a programozónk, akkor megnyitjuk az utat más környezetek számára is, ellenben ez nem igaz a statikusan típusos nyelvekre, ahol nincs ilyen kiskapunk. Gondolkozzunk tehát úgy, hogy azt más nyelvekben is használni tudjuk. Az egyik megoldás, hogyha a konstruktorunkban nem magát az osztályt kötjük ki, azaz typehinteljük, hanem az interfészt, amit az implementál.

Typehint: A PHP 5 óta lehetőségünk van arra, hogy az objektumaink metódusaiban kikényszerítsük a kapott paraméterek típusát. Lehet adott osztályt, implementált interfészt, tömböt, vagy meghívható függvényt kikötni.

Így ha van két osztályunk, amik egyazon interfészt implementálják, akkor gond nélkül át tudjuk adni azt.

1
2
3
4
5
6
7
8
9
10
interface KaveLeloHely {}
class KaveAutomata implements KaveLeloHely {}
class KotyogosKaveFozo implements KaveLeloHely {}

interface FejlesztoiKornyezet {}
class Eclipse implements FejlesztoiKornyezet {}
class NetBeans implements FejlesztoiKornyezet {}
class Programozo {
        public function __construct(KaveLeloHely $kave, FejlesztoiKornyezet $ide) {}
}

A fenti példában definiáltunk két interfészt, amiket implementálnak az osztályaink, ezáltal a konstruktorban megadott helyre injektálhatóak. Ez kicsit sok kódnak tűnhet egy osztály igényeinek kielégítésére, így okkal merülhet fel egyeskben a kérdés:

Mi lenne, ha csak szimplán példányosítanám a konstruktoron belül?

1
2
3
4
5
6
class DatabaseAndStuff {

    public function __construct() {
        $db = new SomeSQLAdapterIMade();
    }
}

A fenti példa már kicsit közelebb áll a valósághoz, itt egy SQL adaptert példányosítunk a konstruktorunkon belül, viszont ez a módszer az előbbinél rosszabb, hiszen ismét bebetonozzuk az osztályunkat (tight coupling). Képzeljük el, hogy a kódunkat át akarjuk vinni egy másik környezetbe, ahol nem SQL-ből nyernénk ki az adatokat, hanem NoSQL megoldással, netán egy SQL kapcsolat nélküli környezetben tesztelnénk azt mock object-el? Nos akkor bizony át kell írjuk a kódunkat, azt pedig nem szeretnénk. PHP esetében nem túl bonyolult, de az előbb beszéltük, hogy rugaszkodjunk el tőle és gondolkodjunk más nyelvekkel. Nos egy JAR fájlban (vagy akár PHAR, ha már PHP) nem nagyon fogunk turkálni, így ez a megoldás biztos nem jöhet számításba.

A legközelebbi cikkben bele fogok nyúlni más tervezési mintákba is, hogy még tovább vigyük a megoldást. A dolog lényege, hogy egy factory-t fogunk használni az osztályon belül (csinálhatjuk kívülről is, erre is kitérek majd), de a factory osztályt az osztályon kívül fogjuk konfigurálni, így téve a kódot újrahasznosíthatóvá.

comments powered by Disqus