2012-05-23 12 views
12

Sto cercando di comprendere il principio Open/Closed (nel mio caso per PHP, ma questo non fa davvero la differenza).Principio aperto/chiuso - Come chiamare le nuove versioni?

Il modo in cui ho capito è che una classe è mai aperta per la modifica. Solo per correggere bug. Se volevo aggiungere un nuovo codice alla classe, dovrei crearne uno nuovo ed estendere la "vecchia" classe. Questo è l'unico modo in cui posso aggiungere un nuovo codice.

In un modo posso vedere i vantaggi di questo. Perché in pratica crei una sorta di sistema di controllo delle versioni, in cui il vecchio codice funziona sempre, ma puoi sempre provare a utilizzare anche le nuove classi.

Ma come funziona in pratica? Voglio dire, supponiamo di avere la seguente classe:

class MyObject 
{ 
    public function doSomething() 
    { 
     echo 'Im doing something'; 
    } 
} 

Così da qualche parte sto probabilmente istanziare questa classe:

$obj = new MyObject(); 

Ma poi decido che è bene avere un altro metodo di quell'oggetto. Quindi posso fare anche qualcos'altro. Secondo l'OCP non posso modificare la classe. Quindi devo crearne uno nuovo, che si estende al vecchio giusto?

Primo problema. Come chiamo la nuova classe? Perché non è davvero un nuovo oggetto completo. Piace. un oggetto Utente è un oggetto Utente. Non posso improvvisamente dargli un nome completamente diverso solo perché ha bisogno di un altro metodo. In ogni caso, creo la nuova classe:

class MyNewObject extends MyObject 
{ 
    public function doSomethingElse() 
    { 
     echo 'Im doing something else now'; 
    } 
} 

Ora, questo significa anche che devo modificare la riga di codice in cui i istanziato la classe "MyObject" e sostituirlo con la classe "MyNewObject", giusto ..? E se ciò avviene in più di un posto, allora devo cercare attraverso il mio codice sorgente ... (Pensa a un metodo in una classe controller, che usa quasi sempre la parola chiave 'new' per istanziare certe classi).

Lo stesso vale per l'ereditarietà. Dovrei trovare ogni classe che eredita la vecchia classe e sostituirla con la nuova classe.


Quindi, in pratica le mie domande sono:

Come si fa il nome delle nuove classi che hanno i nuovi metodi? Solo perché ho aggiunto alcune nuove funzionalità, non significa che posso dare alla classe un nome completamente nuovo ...

E se le classi "vecchie" sono istanziate (o ereditate) da più posti. Allora dovrei trovare tutti quei posti ... Dov'è il guadagno?

+1

'$ x = new MyObj_version_3.14159265()'? mmmmm. torta. –

+1

Nota che ci sono due modi per interpretare il [principio aperto/chiuso] (http://en.wikipedia.org/wiki/Open/closed_principle). Sembra che tu stia pensando principalmente a quello più vecchio (Meyers), mentre quel caso d'uso è solitamente gestito dalla delega al giorno d'oggi. –

+0

Sarebbe più semplice rispondere a questa domanda con un esempio più realistico. Nominare, ecc. Sarebbe più ovvio, penso. – Fuhrmanator

risposta

3

L'Open Closed Principle non deve essere utilizzato come un tipo di sistema di controllo della versione. Se hai davvero bisogno di apportare modifiche alla classe, vai avanti e apporta le modifiche. Non è necessario creare nuove classi e modificare tutte le posizioni che hanno istanziato quella precedente.

Il punto del principio Open Closed è che un sistema ben progettato non dovrebbe richiedere di modificare la funzionalità esistente per aggiungere nuove funzionalità. Se stai aggiungendo una nuova classe al sistema, non dovresti aver bisogno di cercare attraverso tutto il tuo codice per trovare i luoghi in cui devi fare riferimento a quella classe o avere casi speciali per essa.

Se il design della classe non è abbastanza flessibile da gestire alcune nuove funzionalità, è necessario modificare il codice della classe. Ma quando si modifica il codice, renderlo flessibile in modo da poter gestire modifiche simili in futuro senza modifiche al codice. È pensato per essere una politica di design e non un insieme di manette per impedirti di apportare modifiche. Con le buone decisioni di progettazione, nel tempo il codice esistente richiederà sempre meno modifiche quando si aggiungono nuove funzionalità al sistema. È un processo iterativo.

0

È necessario creare un costruttore nella classe MyNewObject che chiama il costruttore genitore di classe:

function __construct() { 

    parent::__construct(); 

} 

In questo modo è possibile creare un'istanza della nuova classe e ancora accedere a tutte le funzionalità di quella estesa.

È inoltre possibile eseguire l'override di qualsiasi funzione nella classe padre (purché non sia contrassegnata come finale, ovviamente).

Così si potrebbe fare:

$newObj = new MyNewObject(); 

$newObj->doSomething(); 

$newObj->doSomethingElse(); 
+0

Sì, so che devo chiamare il costruttore dei genitori. Altrimenti l'ereditarietà non sarebbe molto utile :) --- Ma nel tuo esempio stai anche creando un'istanza di "MyNewObject" invece di "MyObject". Il che rende sence ofcourse, perché vuoi usare la nuova funzionalità. Ma ciò significa anche che devi modificare ogni riga del codice in cui hai creato un'istanza "MyObject". Dovresti cambiarlo ora in "MyNewObject". Questo non mi sembra davvero OO per me se devi cercare attraverso il tuo intero codice per cambiare le vecchie istanziazioni a quelle nuove. – w00

+0

Sì, capisco il tuo punto. Perdonami per aver pensato che fossi un po 'nuovo per OOP. – Steven1978

3

Direi che con l'aggiunta di una funzione, non si sta modificando il comportamento della classe.

In tutti i casi in cui doSomething() viene attualmente chiamato nella tua app, semplicemente aggiungendo doSomethingElse() alla classe non avrà alcun effetto. Dato che non stai cambiando doSomething(), il comportamento è lo stesso di prima.

Una volta determinato che l'implementazione doSomething() non la sta tagliando per determinate circostanze, è possibile estendere la classe e sovrascrivere doSometing(). Di nuovo, l'originale si comporta ancora come sempre, ma ora hai anche un nuovo doSomething() con cui lavorare.

Mi rendo conto che questo va contro la definizione rigorosa di aperto/chiuso, ma questo è il mondo reale, ed è così che ho interpretato quel principio nel mio codice.

+0

Sono d'accordo, aggiungere un nuovo metodo non è una rottura. Non nell'esempio che ho dato. Ma se ho bisogno di fare alcune modifiche al codice in un metodo, allora dovrei creare una nuova classe che faccia quella nuova cosa che voglio. Ma poi le mie due domande rimangono ancora. Come nomino la nuova classe? E questo significa che devo cercare attraverso il mio intero codice per scoprire dove ho istanziato la vecchia classe così posso cambiarla con quella nuova? L'ultimo è quello che mi infastidisce di più in realtà ... Quindi non vedo benefici in questo principio. – w00

+1

Dovresti pensare al contrario: se la tua sottoclasse non ha un nome evidente, forse non dovresti farlo!Le sottoclassi non dovrebbero essere usate per correggere classi rotte - la classe rotta dovrebbe essere riparata, e anche il codice di chiamata che dipende dal comportamento interrotto dovrebbe essere risolto. (Ciò detto, a volte è necessario sottoclasse per correggere una classe rotta, ma di solito non è possibile controllare la classe genitrice o il suo codice di chiamata.) –

+1

BTW, due buoni motivi per creare una sottoclasse: per realizzare una nuova implementazione di una classe base più astratta (eredità polimorfica), o per creare un sottotipo che ha funzionalità extra ma che obbedisce al [Principio di sostituzione di Liskov] (http://en.wikipedia.org/wiki/Liskov_substitution_principle). In entrambi i casi, un nuovo nome dovrebbe essere ovvio. –

Problemi correlati