PHP è un linguaggio orientato agli oggetti, o almeno in qualche modo supporta ragionevolmente la programmazione orientata agli oggetti.
L'idea principale di OOP è che gli oggetti inviano messaggi ad altri oggetti e tali oggetti rispondono a tali messaggi. La metafora di "messaggistica" è azzeccata: quando invii a qualcuno un messaggio, tutto ciò che puoi vedere è la loro risposta. Non puoi vedere cosa fanno con il messaggio. Potrebbero leggerlo, pensarci bene e rispedirci una risposta (eseguendo una funzione). Potrebbero leggerlo e inviarti una risposta pre-inscatolata (leggendo una proprietà). Possono arruolare l'aiuto di altre persone (inviare messaggi stessi). Possono anche consegnare il messaggio a qualcun altro per rispondere (proxy). Non lo sai Spetta interamente al destinatario del messaggio che cosa fare con quel messaggio.
E questo è ciò che vogliamo fare qui. Vogliamo semplicemente inviare al frammento in questione un messaggio che dice "renditi!" e non ci interessa come il frammento lo fa. Il frammento PHP farà qualcosa di diverso dal frammento <meta>
.
Nel tuo codice, hai "tipi" (o "classi") diversi di "cose" (o "oggetti"): hai frammenti CSS, frammenti JavaScript e così via. E tu switch
tra l'esecuzione di diversi pezzi di codice a seconda della "classe" della cosa con cui hai a che fare. Per i CSS, fai una cosa, per JS, fai una cosa diversa.
Tuttavia, tale funzionalità è già integrata in PHP stesso! Quando scrivi:
$foo->bar();
Quindi PHP eseguirà diverse varianti di bar
a seconda di quale sia la classe di $foo
. Stai duplicando il lavoro che PHP già fa per te.
Potresti fare qualcosa di simile a questo: avere un'interfaccia comune per i diversi tipi di oggetto, che fondamentalmente significa solo che decidi su un nome e una firma per il metodo che vuoi usare. In PHP hai persino la possibilità di documentare questa decisione utilizzando interface
:
interface PageFragment {
public function render();
}
In realtà non hai bisogno di questo, potresti semplicemente avere un gruppo di classi con lo stesso nome di metodo, e funzionerebbe a prescindere, ma documentare la tua interfaccia comune con interface
è bello, perché ti permette di usare digita il suggerimento più avanti, cioè documenta che un metodo richiede un oggetto di tipo PageFragment
e ti dà un posto centrale in cui inserire la documentazione, in modo che tu possa dire agli utenti di interface
quale sia il metodo render()
dovrebbe fare.
Opzionalmente, possiamo aggiungere alcune classi base comuni che contengono alcune funzionalità comuni per tutti i tipi di frammenti di pagina:
abstract class FragmentBase implements PageFragment {
protected $path;
function __construct($path) {
$this->path = $path;
}
// Just as an example
public function __toString() {
return "Page fragment from file {$this->path}";
}
}
Il vero lavoro viene quindi eseguito in sottoclassi / implementazioni diverse di PageFragment
, una classe per ogni tipo di frammento:
class CssFragment extends FragmentBase {
public function render() {
print("… do stuff to render CSS from file {$this->path}" . PHP_EOL);
}
}
class MetaFragment extends FragmentBase {
public function render() {
print("… do stuff to render <meta> elements from file {$this->path}" . PHP_EOL);
}
}
class HeaderJsFragment extends FragmentBase {
public function render() {
print("… do stuff to render JavaScript in the header from file {$this->path}" . PHP_EOL);
}
}
class BodyJsFragment extends FragmentBase {
public function render() {
print("… do stuff to render JavaScript in the body from file {$this->path}" . PHP_EOL);
}
}
class PhpFragment extends FragmentBase {
public function render() {
print("… do stuff to include PHP from file {$this->path} on the page" . PHP_EOL);
}
}
Ora, se qualche parte del tuo framework da qualche altra parte nella tua base di codice crea dei frammenti di pagina:
$fragment1 = new PhpFragment("/path/to/foo.php");
$fragment2 = new CssFragment("/path/to/foo.css");
e ne mandi un po 'a te:
$fragments = [$fragment1, $fragment2];
Non hai bisogno di sapere, e non ti importa se sono frammenti CSS o frammenti di PHP o altro. Non hai bisogno di sapere cosa sono, perché loro sanno cosa sono e sanno come gestirsi da soli. Digli semplicemente di fare qualcosa e fanno la cosa giusta, automaticamente:
foreach ($fragments as $fragment) $fragment->render();
// … do stuff to include PHP from file /path/to/foo.php on the page
// … do stuff to render CSS from file /path/to/foo.css
Come puoi vedere, ogni frammento ha fatto la cosa giusta, e ogni frammento ha fatto una diversa cosa, senza che ci fosse un singolo condizionale nell'intero codice sorgente!
Questo tipo di modifica della struttura del codice da un condizionale esplicito rispetto a qualche nozione di "tipo" al dispiegamento del metodo polimorfico su tipi reali è chiamato Sostituisci condizionale con Refilloring polimorfismo :
da
function getSpeed() {
switch ($type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() * $numberOfCoconuts;
case NORWEGIAN_BLUE:
return ($isNailed) ? 0 : getBaseSpeed($voltage);
}
throw new RuntimeException("Should be unreachable");
}
a
interface Bird {
public function getSpeed();
}
class European implements Bird {
public function getSpeed() {
return $this->getBaseSpeed();
}
}
class African implements Bird {
public function getSpeed() {
return $this->getBaseSpeed() - $this->getLoadFactor() * $this->numberOfCoconuts;
}
}
class NorwegianBlue implements Bird {
public function getSpeed($voltage = 0.0) {
return ($this->isNailed) ? 0 : $this->getBaseSpeed($voltage);
}
}
Si noterà che è possibile ridefinire ulteriormente NorwegianBlue
in questo modo:
class NorwegianBlue implements Bird {
public function getSpeed($voltage = 0.0) {
return $this->getBaseSpeed($voltage);
}
}
class NailedNorwegianBlue extends NorwegianBlue {
public function getSpeed($voltage = 0.0) {
return 0;
}
}
Esiste una campagna un po 'divertente chiamata Anti- IF
Campaign che sostiene la programmazione in questo stile.
Una domanda che potresti porsi è: funziona sempre? Posso sempre sostituire i condizionali con il polimorfismo? E la risposta è: sì, puoi! Il Refactoring Sostituisci condizionale con polimorfismo parla solo di condizionali che attivano una nozione di "tipo", ma in realtà, l'opzione ogni può essere interpretata come un cambio di tipi e quindi refactored allo stesso modo. Ci sono, infatti, lingue che non hanno nemmeno i condizionali, come Smalltalk. E infatti, se PHP non avesse condizionali, potresti implementarli tu stesso usando nient'altro che il dispiegamento del metodo polimorfico:
interface Buul {
public function ifThenElse(callable $thenBranch, callable $elseBranch);
public function andand(callable $other);
public function oror(callable $other);
public function negate();
}
final class TrueClass implements Buul {
public function ifThenElse(callable $thenBranch, callable $elseBranch) {
return $thenBranch();
}
public function andand(callable $other) { return other(); }
public function oror(callable $other) {}
public function negate() { return FalseClass::instance(); }
// please excuse the rather crude singleton implementation ;-)
private static $instance;
public static function instance() {
if (isset($instance)) return $instance;
return $instance = new TrueClass();
}
}
final class FalseClass implements Buul {
public function ifThenElse(callable $thenBranch, callable $elseBranch) {
return $elseBranch();
}
public function andand(callable $other) {}
public function oror(callable $other) { return other(); }
public function negate() { return TrueClass::instance(); }
private static $instance;
public static function instance() {
if (isset($instance)) return $instance;
return $instance = new FalseClass();
}
}
function buul($bool) { return $bool ? TrueClass::instance() : FalseClass::instance(); }
buul(4 < 3)->ifThenElse(function () { print("less" . PHP_EOL); }, function () { print("greater" . PHP_EOL); });
// greater