Il Sat, 25 Apr 2015 14:56:21 +0200, alex ha scritto:
>> Se domani vuoi usare una classe diversa ma con la stessa funzione?
>>
>>
> ClasseDiversa::sum();
> La uso, qual'è il problema?
Che se la usi in 1000 file diversi te la devi andare a cercare ovunque.
>> Per esempio, una che supporta la somma tra numeri immaginari. Ti tocca
>> cercare tutte le chiamate statiche nel tuo codice e cambiarle con la
>> chiamata alla nuova classe.
> Chiedo scusa, ma avrai già intuito che a volte faccio fatica a seguirti.
> Certo tu (ma anche gli altri) mi stai facendo notare delle cose molto
> interessanti, ma se non cerchi di esprimerti con un po' di chiarezza,
> non riesco a centrare pienamente il concetto
bootstrap.php
$app = new App();
$app->set('somma', new Sommante());
$qualcosa = new Qualcosa($app);
$somma = $qualcosa->fai(10, 20);
...
Qualcosa.php (e altri 1000 file):
class Qualcosa {
public function __construct(App $app)
{
$this->app = $app;
}
public function fai($a, $b) {
$res = $this->app->get('somma')->sum($a, $b);
}
}
Un domani...
class SommanteImmaginario {
public function sum($a, $b) {
if ($a instanceof ImgNumber && $b instanceof ImgNumber) {
// fai la somma tra immaginari
} else {
return $a + $b
}
}
}
modifiche al tuo codice: 1, in bootstrap.php
$app->set('somma', new SommanteImmaginario());
Questo si chiama Dependency Injection (DI). Inietti un oggetto di classe
App in altre classi, e lui fa da Dependency Injection Container (DIC)
tenendo una qualsivoglia copia di Sommante e fornendola a chi la richiede.
Ma spingiamoci oltre:
interface Sommabile {
public function sum($a, $b);
}
class Sommante implements Sommabile { ... }
class SommanteImmaginario implements Sommabile {
public $orig = null;
public function __construct(Sommabile $originale) {
$this->orig = $originale;
}
public function sum($a, $b) {
if ($a instanceof ImgNumber && $b instanceof ImgNumber) {
// fai la somma tra immaginari
} else {
return $this->orig->sum($a, $b);
}
}
}
Il tuo bootstrap diventerà:
$app->set('somma', new SommanteImmaginario(new Sommante()));
Questo si chiama Decoratore, e si usa solitamente con la DI di cui sopra.
Programmi a contratto (l'interface Sommabile, che garantisce di avere una
funzione sum che prende due parametri). A questo punto che tu passi
Sommante o SommanteImmaginario non fa differenza, perché poi lui si
aspetta un Sommabile. SommanteImmaginario è un Sommabile che accetta un
altro Sommabile da wrappare/decorare. Puoi metterne quanti ne vuoi nel
bootstrap, e il resto del codice (se rispetta il contratto) non avrà
nessun problema a gestirlo.
Queste cose semplicemente non le puoi fare con le classi statiche.
Puoi avere la tentazione di usare App come statica, e per progetti medio-
piccoli ci sta e lo faccio anche io. Difficilmente cambierà la struttura
portante dell'applicazione, e se cambia faccio prima a riscrivere
l'applicazione che a fare refactoring di tutto.
Ma, per fare l'esempio del tuo configuratore, puoi usare una classe base
con getter e setter da usare nel bootstrap, e poi decorarla con una che
legge anche da un file YAML, a sua volta decorata da un'altra che fa
l'override dei valori prendendoli da DB, a sua volta decorata da un'altra
che fa caching di tutto l'ambaradan su memcache per non dover fare il
parsing ad ogni connessione.
Avresti
interface ConfigurationManager {
public function set($k, $v);
public function get($k);
}
class BasicConfig implements ConfigurationManager {
protected $config = array();
public function set($k, $v) { $this->config[$k] = $v; }
public function get($k) { return (@$this->config[$k] ?: null); }
}
class YAMLConfig implements ConfigurationManager {
protected $wrapped, $yamlFile, $parsed = false;
public function __construct($yamlFile, $wrapped) {
$this->wrapped = $wrapped;
$this->yamlFile = $yamlFile;
}
public function get($k) {
if (!$this->parsed) { $this->parseYaml(); }
$this->wrapped->get($k);
}
public function set($k, $v) {
if (!$this->parsed) { $this->parseYaml(); }
$this->wrapped->set($k, $v);
}
public function parseYaml($yamlFile) { ...; $this->parsed = true; }
}
class DBConfig implements ConfigurationManager {
protected $pdo, $wrapped, $parsed = false, $tableName;
public function __construct($pdo, $tableName, $wrapped) {
$this->wrapped = $wrapped;
$this->pdo = $pdo;
$this->tableName = $tableName;
}
public function get($k) {
if (!$this->parsed) { $this->parseDb(); }
$this->wrapped->get($k); }
}
public function set($k, $v) {
if (!$this->parsed) { $this->parseDb(); }
$this->wrapped->set($k, $v); }
}
public function parseDb() { ... }
}
class MemcachedConfig implements ConfigurationManager {
protected $memcache, $ttl, $wrapped;
public function __construct($memcache, $ttl, $wrapped) {
$this->wrapped = $wrapped;
$this->memcache = $memcache;
$this->ttl = $ttl;
}
public function get($k) {
if (time() < $ttl) {
return $this->getFromMemcache($k);
} else {
return $this->wrapped->get($k);
}
}
public function set($k, $v) {
$this->ttl = 0;
$this->wrapped->set($k, $v);
$this->saveAllToMemcache();
}
public function getFromMemcache($k) { ... }
public function saveAllToMemcache() { ... }
}
E, finalmente, nel bootstrap:
$config =
new MemcachedConfig(
new Memcached('blablabla'),
5*60,
new DbConfig(
new PDO('sqlite:/tmp/config.db'),
'configuration',
new YamlConfig(
'/etc/myapp.config.yaml',
new Config()
)
)
)
;
Vuoi testare senza memcache? Basta commentare le righe 2, 3, 4 a la
penultima (o scrivere il codice senza Piramidi di Paura e commentare solo
una riga).
Vuoi togliere il DB? Commenti la riga che decora con DbConfig.
Ecc. ecc.
Per questo ti dicevo che trovo il tuo controllo abbastanza inutile. Una
classe deve fare il minimo indispensabile.
Vuoi aggiungere il controllo? Fai un decoratore che, al set(), verifica
prima con un get() se il valore c'è già ed eventualmente lancia
un'eccezione.
Bye.
PS.: Ecco, oggi mi stavo decidento a scrivere un articolo sul blog e me
l'hai fatto scrivere qui. :P