Rozdělení souvisejících metod

Školení, která pořádám

Adminer používá pro práci s databázovým systémem tři druhy metod:

Metody specifické pro extenzi
Např. metoda query se s extenzí MySQL přeloží na mysql_query.
Metody specifické pro databázový systém
Např. getTables se v MySQL přeloží na SHOW TABLES, s jinými systémy to může položit dotaz do databáze information_schema.
Společné metody
Např. metoda getVals položí dotaz a jeho výsledek vrátí v poli.

Jak tyto tři druhy metod propojit?

Jedna velká třída

Vytvoříme abstraktní třídu se společnými metodami, jejího potomka s metodami specifickými pro systém a jejího potomka s metodami pro extenzi. Asi takhle (kód je zjednodušen):

<?php
abstract class Connection {
    abstract function query($query);
    abstract function getTables();
    function getVals($query) {
        $this->query($query);
        // ...
    }
}

abstract class ConnectionMysql extends Connection {
    function getTables() {
        return $this->getVals("SHOW TABLES");
    }
}

class ConnectionMysqlMysql extends ConnectionMysql {
    function query($query) {
        return mysql_query($query);
    }
}

$connection = new ConnectionMysqlMysql;
$connection->query("");
$connection->getTables();
$connection->getVals("");
?>

Nevýhodu vidím v tom, že je všechno smíchané pohromadě.

Dvě nezávislé třídy

<?php
abstract class Database {
    protected $connection;
    function __construct(Connection $connection) {
        $this->connection = $connection;
    }
    abstract function getTables();
    function getVals($query) {
        $this->connection->query($query);
        // ...
    }
}

class DatabaseMysql extends Database {
    function getTables() {
        return $this->getVals("SHOW TABLES");
    }
}

interface Connection {
    function query($query);
}

class ConnectionMysql implements Connection {
    function query($query) {
        return mysql_query($query);
    }
}

$connection = new ConnectionMysql;
$database = new DatabaseMysql($connection);
$connection->query("");
$database->getTables();
$database->getVals("");
?>

Nelíbí se mi, že se v metodách uvnitř Database (kterých je naprostá většina) musí přistupovat k vlastnosti $connection (kterou potřebují skoro všechny metody).

Statické metody

Společné metody by mohly být statické a dostávat konexi v parametru. Tím pádem by to rovnou mohly být globální funkce.

<?php
function getVals($connection, $query) {
    $connection->query($query);
    // ...
}

abstract class Database {
    protected $connection;
    function __construct(Connection $connection) {
        $this->connection = $connection;
    }
    abstract function getTables();
}

class DatabaseMysql extends Database {
    function getTables() {
        return getVals($this->connection, "SHOW TABLES");
    }
}

interface Connection {
    function query($query);
}

class ConnectionMysql implements Connection {
    function query($query) {
        return mysql_query($query);
    }
}

$connection = new ConnectionMysql;
$database = new DatabaseMysql($connection);
$connection->query("");
$database->getTables();
getVals($connection, "");
?>

Nelíbí se mi, že musím společným metodám (nebo funkcím) konexi předávat v parametru.

Jak je to jinde

Koukal jsem se, jak je to udělané v Dibi, a to se mi moc nelíbí. Společné metody jsou v jedné třídě, metody specifické pro extenzi v druhé třídě – to je v pořádku. Metody specifické pro databázový systém (např. getIndexes) jsou ale zkopírované do ovladačů pro jednotlivé extenze – programování metodou copy–paste se snažím zásadně vyhýbat. Podle mě tyto metody do Dibi ani nepatří, to ale řešení neospravedlňuje.

V Admineru to je zatím tak, že kromě metod specifických pro extenzi jde o globální funkce používající globální proměnnou, což je asi nejúspornější, ale nelíbí se mi to.

Jakub Vrána, Řešení problému, 26.5.2010, diskuse: 4 (nové: 0)

Diskuse

ikona v6ak:

Dodám, že prvnímu přístupu se říká dědičnost a části druhého přístupu se říká kompozice.
Vzhledem k větším možnostem, logice OOP (auto není nějaký volant, auto obsahuje volant) a jednoduché dědičnosti v mnoha jazycích bývá upřednostňována spíše kompozice.

ikona David Grudl:

Doplním odkazy, kam kritika míří:

- http://api.dibiphp.com/1.3/__filesource/fsource_….php.html#a433
- http://api.dibiphp.com/1.3/__filesource/fsource_….php.html#a425

Ano, v dibi mají skutečně čtyři metody stejnou implementaci a to v ovladači MySql a MySqli. To je spíš shoda okolností. Shodou okolností se ve dvou ovladačích zjišťují meta informace stejným způsobem.

Čímž chci říct, že mi naopak jako nešťastné připadá navrhovat celou architekturu podle takovéto drobnosti.

Připadá mi nešťastné použít dědičnost proto, abych odstranil dvojí stejný kód.

A je-li to možné, raději se vyhnu použití abstraktních tříd.

(v případě dibi ovladačů pro mysql by dědičnost měla svou logiku a jako vedlejší efekt by mohla vyřešit duplicitu oněch metod, ale opačně to neplatí, tj. duplicita neimplikuje dědičnost).

ikona Jakub Vrána OpenID:

Moje primární motivace při používání skoro všech programátorských struktur (počínaje cykly, přes funkce až k třídám) je zabránit opakování kódu, které považuji za zlo. Přidělává totiž práci a vede k chybám v případě potřeby změny kódu.

Takže architektura může být taková nebo onaká, vždycky ale musí skončit kódem na jednom místě. Opakovat se můžou jedině identifikátory (např. názvy funkcí).

jos:

make install

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.