Terve,
Mitä eroa seuraavilla toteutuksilla ja kumpi niistä on parempi ja miksi?
Kaiki nuo frameworkit, mitä tuossa pikaisesti katselin, näyttäisi toteuttaneen lazy loadingin jälkimmäisellä tavalla.
abstract class DatabaseConnectionAbstract { private $connection = null; private $connectionParameters = array(); public function __construct(array $connectionParameters) { $this->connectionParameters = $connectionParameters; } public function query($statement) { $this->getConnection->query($statement); } private function getConnection() { if ($this->connection === null) { $this->connection = $this->createConnection(); } return $this->connection; } abstract protected function createConnection(); } class DatabaseConnectionPDOMySQL extends DatabaseConnectionAbstract { public function __construct($connectionParameters) { parent::__construct($connectionParameters) } protected function createConnection() { return new PDO($this->connectionParameters['dsn'], $this->connectionParameters['username'], $this->connectionParameters['password']); } }
class DatabaseConnection { private $connection = null; private $databaseConnectionFactory = null; public function __construct(DatabaseConnectionFactoryInterface $databaseConnectionFactory) { $this->databaseConnectionFactory = $databaseConnectionFactory; } public function query($statement) { $this->getConnection->query($statement); } private function getConnection() { if ($this->connection === null) { $this->connection = $this->databaseConnectionFactory->createConnection(); } return $this->connection; } } class DatabaseConnectionFactoryPDOMySQL implements DatabaseConnectionFactoryInterface { private $connectionParameters; public function __construct($connectionParameters) { $this->connectionParameters = $connectionParameters; } public function createConnection() { return new PDO($this->connectionParameters['dsn'], $this->connectionParameters['username'], $this->connectionParameters['password']); } } interface DatabaseConnectionFactoryInterface { public function createConnection(); }
Jälkimmäisessä injektoidaan $databaseConnectionFactory DatabaseConnection:lle, minkä johdosta sinun ei tarvitse ohjelmakoodissasi muuttaa mitään jos haluatkin käyttää jotain muuta kuin DatabaseConnectionFactoryPDOMySQL:ää (ohjelmakoodista ei tule kovakoodattua DatabaseConnectionPDOMySQL:ää vasten).
DI:tä käytettäessä yksikkötestaus onnistuu myös kuten pitää, sillä voit testatessa mockata DatabaseConnectionFactoryInterfacen pohjalta.
Tässä nähdäkseni tärkeimmät syyt alemman eduksi.
Liekkö johtui vain esimerkin yksinkertaistamisesta, mutta kuitenkin tuolla on sellainen ongelma, että mistä pirusta sitä äkkiseltään tietää että databaseConnectionFactory:lla pitää sellainenkin metodi olla olemassa kuin "query", sillä se ei käy suoraan DatabaseConnectionFactoryInterface:n määrittelystä ilmi. Mutta tämä nyt vain tällaisena sivuhuomautuksena.
timoh kirjoitti:
Liekkö johtui vain esimerkin yksinkertaistamisesta, mutta kuitenkin tuolla on sellainen ongelma, että mistä pirusta sitä äkkiseltään tietää että databaseConnectionFactory:lla pitää sellainenkin metodi olla olemassa kuin "query", sillä se ei käy suoraan DatabaseConnectionFactoryInterface:n määrittelystä ilmi. Mutta tämä nyt vain tällaisena sivuhuomautuksena.
Noh, kyseinen metodi on kylläkin DatabaseConnection-luokan metodi kuten kuuluukin. Factory-luokalla ei ole mitään asiaa tietokantakyselyitä suorittamaan.
Itse en taas ymmärrä sitä, miksi DatabaseConnection-luokalla on metodi getConnection()... Toinen ongelma on se, että factory-luokalle annetaan selvästi pdo-riippuvaista dataa dsn-stringinä. Oikeasti kai sille pitäisi antaa tietokannan nimi, palvelimen osoite ja portti kaikki erikseen, ja pdo-yhteysluokka itse muodostaisi tarvitun dsn:n niistä.
Miksi alchemist?
Kyllä kantaan yhteyden saa yhteyden DSN:llä, jos sellainen on luotu. Julkaistu dsn itsessään ohjaa oikeaan palvelimeen etc, ja tietokannan connectionstringiä saa muokattua applikaation ulkopuolella.
Itse pidän dsn:ää paljon parempana keinona kuin suorien connectionstringien käyttämistä, enkä käsitä ylipäätään miksi softan pitäisi liittyä mitenkään dsn nimen generointiin?
No sanoinhan jo: se on pdo-riippuvaista dataa. Jos joskus joudut käyttämään yhteyden ottamiseen jotain muuta matalan tason rajapintaa kuin pdo:ta, niin tuolla dsn-stringillä ei tee yhtään mitään. Nuo factory-luokat ja kaiken muun abstrahoinnin voi heittää mäkeen saman tien, jos kuitenkin aiotaan sitoa toteutus tiukasti pdo:hon jo konfigurointitiedostosta lähtien.
Ainoa asia, joka tuota pdo:n dsn-stringiä tarvitsee, on pdo-ajuri itse. Täten on täysin selvää, ettei sitä pitäisi näkyä missään muualla koodissa.
Sama asia vielä eri tavalla selitettynä: vaihtoehtoja on täsmälleen kaksi:
1. Pdo-ajuri toteuttaa dsn-stringin generoivan koodin.
2. Kaikki muut tietokanta-ajurit toteuttavat parserin, joka osaa purkaa dsn:stä ulos hostnamen, portin, tietokannan nimen ja kaiken muun tiedon, mitä siihen sitten on liitettykään. Ja nämä implementaatiot täytyy korjata AINA kun pdo:n määrittelyt muuttuvat.
Eli jos luot pdo_obdc:llä yhteyden vaikka windows system dsn:ään luotuun dsn nimeen ja vaihdat dsn:n osoittamaan eri kantaan kuin aluksi, softapuolelta pitää korjata implementaatioita ja yhteys ei siirry uuteen valittuun kantaan?
The Alchemist kirjoitti:
Noh, kyseinen metodi on kylläkin DatabaseConnection-luokan metodi kuten kuuluukin.
Hain sitä takaa että DatabaseConnection käyttää "ei merkattua" metodia $this->getConnection->query($statement) ja tässä query() ei tule ilmi interfacesta.
Tuo esimerkkikoodi ei tosiaan juu ole tyylikkäimmästä päästä.
@groovyb Tuossa on kyseessä se että PDO on eroteltuna muista implementaatioista millä kantaan yhdistetään. Voi olla että et joudu tekemään mitään konfiguraatiomuutoksia vaikka vaihdat lennosta mysqli:hin (tietysti olettaen että kantaa veivataan standardin APIn takaa, minkä PDO-pohjaiset implementaatiot sekä mysqli-pohjainen implementaatio toteuttavat).
timoh kirjoitti:
The Alchemist kirjoitti:
Noh, kyseinen metodi on kylläkin DatabaseConnection-luokan metodi kuten kuuluukin.
Hain sitä takaa että DatabaseConnection käyttää "ei merkattua" metodia $this->getConnection->query($statement) ja tässä query() ei tule ilmi interfacesta.
Eihän tuossa edes ole esitelty mitään rajapintaa tai abstraktia perusluokkaa DatabaseConnection-luokalle.
Groovyb ilmeisesti ymmärsi viestistäni yhtä vähän kuin minä hänen omastaan. Jos vaikka yhteys pitää ottaa johonkin muuhun kuin odbc:tä tukevaan tietokantaan, niin dsn-määritykset ovat käyttökelvottomia. Php:n kolmesta eri mysql-rajapinnasta vain pdo tukee dsn:ää. Tai jos pitää saada yhteys vaikka jotain soap-johdannaista käyttäen kolmannen osapuolen yksityiseen tietokantaan, niin dsn on aivan hyödytön. Konffifiluun vain tulee eri tapoja määritellä samat asiat riippuen siitä, mitä rajapintaa milloinkin satutaan käyttämään -> FAIL.
Dsn-string on todellakin jotain, mikä rakennetaan ohjelmallisesti. En tiedä yhten ainutta softaa, johon tietokantayhteys konffattaisiin käyttöliittymän kautta syöttäen suoraan dsn-string erillisten käyttäjätunnusten ja palvelimen yhteysasetusten sijaan.
http://www.w3schools.com/php/php_db_odbc.asp
Tuossa palvelimen yhteysasetukset system dsn:stä.
Tietenkin jos odbc tukea ei ole, tilanne on eri.
Toinen asia on että pitääkö sovellulsen tukea kaikkia kantoja?
Olettaen että vaihdetaan vaikka mysql:stä oracleen tai ms accessiin muuttuu moni muukin asia olennaisesti.
uskottavampi tilanne on se että vaihdetaan kantaa rajapinnan ollessa sama (esim fallback tai backup kantaan vaihdettaessa), jolloin ajuria ei tarvitse itsessään vaihtaa.
The Alchemist kirjoitti:
Eihän tuossa edes ole esitelty mitään rajapintaa tai abstraktia perusluokkaa DatabaseConnection-luokalle.
DatabaseConnectionFactoryInterfaceen perustuva olio injektoidaan DatabaseConnection:lle. Tämä on ehkä helpompi nähdä näin päin, että nyt jos toteuttaa korvaavan implementaation DatabaseConnectionFactoryInterface:n pohjalta, niin se tyssää heti alkuunsa koska et nähnyt että query-metodikin piti olla olemassa (DatabaseConnection käyttää sellaista).
Tarkoitan että koodaus puutteellista rajapintaa vasten ei synnytä kuin savua.
protected function createConnection() { return new PDO($this->connectionParameters['dsn'], $this->connectionParameters['username'], $this->connectionParameters['password']); }
Tuo dsn tosiaan olisi parasta kai pilkkoa vielä osiin(host, port, dbname...), jos siitä haluaa geneerisemmän.
protected function createConnection() { return new PDOMySQLConnection($this->connectionParameters); }
class PDOMySQLConnection { public function __construct($connectionParameters) { return new PDOConnection($this->constructDSN($connectionParameters), $connectionParameters['username'], $connectionParameters['password']) } private function constructDSN($connectionParameters) { return 'mysql:' . $connectionParameters['host'] . ';' . $connectionParameters['port'] ... } }
interface ConnectionInterface { public function query($statement); }
PDOConnection extends \PDO implements ConnectionInterface { public function __construct($dsn, $username, $password) { parent::__construct($dsn, $username, $password); } }
createConnectionin palauttaman arvon tyypistä ei voi olla varma, joten ei voi myöskään varmaksi tietää toteuttaako se tietyn rajapinnan(query tässä tapauksessa). Jos tyypistä haluaa olla varma niin ConnectionAbstract-luokan getConnection-metodiin(jonka olisi tarkoitus toimia ns. proxyna, joka palauttaa alustetun olion vasta, kun sitä oikeasti tarvitaan, luokan sisällä) voisi kirjoittaa jonkin tarkistuksen tyyliin...
private function getConnection() { if ($this->connection === null) { $connection = $this->createConnection(); if ($connection instanceof ConnectionInterface) { $this->connection = $connection; } else { throw new Exception('...'); } } return $this->connection; }
Jokatapauksessa ensimmäisessä esimerkissä on käsittääkseni kyse Factory Method-nimisestä patterinista. Toinen esimerkki on kait Abstract Factory pattern, jos ymmärrän lukemaani kirjaa oikein.
timoh kirjoitti:
DI:tä käytettäessä yksikkötestaus onnistuu myös kuten pitää, sillä voit testatessa mockata DatabaseConnectionFactoryInterfacen pohjalta.
Noniin yksikkötestaus oli hyvä pointti. En tosin ole vielä perehtynyt kunnolla, mutta jälkimmäinen toteutus sopii siis tähän ilmeisesti paremmin.
timoh kirjoitti:
Jälkimmäisessä injektoidaan $databaseConnectionFactory DatabaseConnection:lle, minkä johdosta sinun ei tarvitse ohjelmakoodissasi muuttaa mitään jos haluatkin käyttää jotain muuta kuin DatabaseConnectionFactoryPDOMySQL:ää (ohjelmakoodista ei tule kovakoodattua DatabaseConnectionPDOMySQL:ää vasten).
Jos haluankin käyttää toista tietokantaa niin eikös vain seuraavanlaiset muutokset ole pakollisia koodiin kummassakin tapauksessa?
$databaseConnection = new DatabaseConnectionPDOMySQL($connectionParameters); $databaseConnection = new DatabaseConnectionOCI8($connectionParameters);
$databaseConnection = new DatabaseConnection(DatabaseConnectionFactoryPDOMySQL); $databaseConnection = new DatabaseConnection(DatabaseConnectionFactoryOCI8);
Cartter kirjoitti:
Jos haluankin käyttää toista tietokantaa niin eikös vain seuraavanlaiset muutokset ole pakollisia koodiin kummassakin tapauksessa?
$databaseConnection = new DatabaseConnectionPDOMySQL($connectionParameters); $databaseConnection = new DatabaseConnectionOCI8($connectionParameters);$databaseConnection = new DatabaseConnection(DatabaseConnectionFactoryPDOMySQL); $databaseConnection = new DatabaseConnection(DatabaseConnectionFactoryOCI8);
Näin on, mutta tuohon alkuperäiseen esimerkkiin viitaten, niin rajapintaan perustuva toteutus on sillä selvä, ei tarvitse arvailla mitä metodeja on saatavilla kun $databaseConnection:ia käytetään.
Tietysti ylemmän esimerkin DatabaseConnectionPDOMySQL ja DatabaseConnectionOCI8 voivat toteutta saman abstraktin luokan (ja näin tarjota vastaavan rajapinnan), mutta tämä on mielestäni tässä tapauksessa huonompi idea.
Aihe on jo aika vanha, joten et voi enää vastata siihen.