Come aderire al principio di open-closed in pratica

13

Comprendo l'intento del principio di open-closed. Ha lo scopo di ridurre il rischio di rompere qualcosa che già funziona modificandolo, dicendoti di provare ad estendere senza modificare.

Tuttavia, ho avuto qualche problema a capire come questo principio viene applicato nella pratica. Per quanto ho capito, ci sono due modi per applicarlo. Prima e dopo un possibile cambiamento:

  1. Prima: programma alle astrazioni e "prevedi il futuro" il più possibile. Ad esempio, un metodo drive(Car car) dovrà cambiare se Motorcycle s vengono aggiunti al sistema in futuro, quindi probabilmente viola l'OCP. Ma il metodo drive(MotorVehicle vehicle) è inferiore probabilmente dovrà cambiare in futuro, quindi aderisce a OCP.

    Tuttavia, è abbastanza difficile prevedere il futuro e conoscere anticipare le modifiche che verranno apportate al sistema.

  2. Dopo: quando è necessaria una modifica, estendi una classe invece di modificarla codice corrente.

La pratica n. 1 non è difficile da capire. Comunque è la pratica n. 2 che ho difficoltà a capire come applicare.

Ad esempio (l'ho preso da un video su YouTube): diciamo che abbiamo un metodo in una classe che accetta CreditCard oggetti: makePayment(CraditCard card) . Un giorno Voucher s vengono aggiunti al sistema. Questo metodo non li supporta quindi deve essere modificato.

Quando implementavamo il metodo, in primo luogo, non riuscivamo a prevedere il futuro e il programma in termini più astratti (ad esempio makePayment(Payment pay) , quindi ora dobbiamo cambiare il codice esistente.

La pratica n. 2 dice che dovremmo aggiungere la funzionalità estendendo invece di modificare. Che cosa significa? Devo creare una sottoclasse della classe esistente invece di modificare semplicemente il codice esistente? Dovrei creare una sorta di wrapper attorno ad esso solo per evitare di riscrivere il codice?

O il principio non si riferisce nemmeno a 'come modificare / aggiungere correttamente funzionalità', ma piuttosto si riferisce a 'come evitare di dover apportare modifiche in primo luogo (es. programma alle astrazioni)?

    
posta Aviv Cohn 12.05.2014 - 15:35
fonte

3 risposte

13

I principi di progettazione devono sempre essere bilanciati l'uno con l'altro. Non puoi predire il futuro, e la maggior parte dei programmatori lo fa orribilmente quando ci provano. Ecco perché abbiamo la regola di tre , che riguarda principalmente la duplicazione, ma si applica al refactoring per qualsiasi altro principi di progettazione pure.

Quando hai solo un'implementazione di un'interfaccia, non devi preoccuparti troppo di OCP, a meno che non sia chiaro dove si sarebbero verificate eventuali estensioni. In effetti, spesso perdi la chiarezza quando cerchi di sovrascrivere in questa situazione. Quando lo estendi una volta, ti rifatti per renderlo OCP se è il modo più semplice e chiaro per farlo. Quando lo estendi a una terza implementazione, assicurati di rifattorizzarlo prendendo in considerazione l'OCP, anche se richiede un piccolo sforzo in più.

In pratica, quando hai solo due implementazioni, il refactoring quando aggiungi un terzo di solito non è troppo difficile. È quando lasci che cresca oltre quel punto che diventa fastidioso da mantenere.

    
risposta data 12.05.2014 - 16:38
fonte
2

Penso che tu stia guardando troppo lontano nel futuro. Risolvi il problema attuale in un modo flessibile che aderisce ad open / closed.

Supponiamo che tu debba implementare un metodo drive(Car car) . A seconda della lingua, hai un paio di opzioni.

  • Per le lingue che supportano l'overloading (C ++), quindi usa solo drive(const Car& car)

    A un certo punto più tardi potresti aver bisogno di drive(const Motorcycle& motorcycle) , ma non interferirà con drive(const Car& car) . Nessun problema!

  • Per le lingue che non supportano l'overloading (obiettivo C), quindi includi il nome del tipo nel metodo -driveCar:(Car *)car .

    A un certo punto più tardi potresti aver bisogno di -driveMotorcycle:(Motorcycle *)motorcycle , ma, di nuovo, non interferirà.

Ciò consente a drive(Car car) di essere chiuso alla modifica, ma è aperto all'estensione ad altri tipi di veicoli. Questa pianificazione minimalista del futuro che ti consente di svolgere il lavoro oggi, ma ti impedisce di bloccarti in futuro.

Cercare di immaginare i tipi più basilari di cui hai bisogno può portare a regressioni infinite. Cosa succede quando vuoi guidare un Segue, una bicicletta o un Jumbo jet. Come si costruisce un singolo tipo astratto generico che può tenere conto di tutti i dispositivi che le persone entrano e utilizzano per la mobilità?

    
risposta data 12.05.2014 - 17:23
fonte
2

I understand the intent of the open-closed principle. It's meant to reduce the risk of breaking something that already works while modifying it, by telling you to try to extend without modifying.

Si tratta anche di non rompere tutti gli oggetti che si basano su quel metodo non cambiando il comportamento di oggetti già esistenti. Una volta che un oggetto ha pubblicizzato il comportamento, la modifica è rischiosa poiché stai alterando il comportamento conosciuto e previsto dell'oggetto senza sapere esattamente quali altri oggetti si aspettano tale comportamento.

What does that mean? Should I subclass the existing class instead of simply changing it's existing code?

Sì.

"Accetta solo carte di credito" è definito come parte del comportamento di quella classe, attraverso la sua interfaccia pubblica. Il programmatore ha dichiarato al mondo che il metodo di questo oggetto prende solo le carte di credito. Lo ha fatto usando un tipo di metodo non particolarmente chiaro, ma è stato fatto. Il resto del sistema si basa su questo.

Poteva aver senso al momento, ma ora se ha bisogno di cambiare ora dovresti creare una nuova classe che accetti cose diverse dalle carte di credito.

Nuovo comportamento = nuova classe

A parte - Un buon modo per predire il futuro è pensare al nome che hai dato a un metodo. Hai dato un nome di metodo sonoro davvero generale come makePayment a un metodo con regole specifiche nel metodo per sapere esattamente quale pagamento può effettuare? Questo è un odore di codice. Se hai regole specifiche queste dovrebbero essere chiarite dal nome del metodo - makePayment dovrebbe essere makeCreditCardPayment. Fai questo quando stai scrivendo l'oggetto la prima volta e gli altri programmatori ti ringrazieranno per questo.

    
risposta data 22.05.2014 - 20:22
fonte

Leggi altre domande sui tag