Applicazione di DRY a una gerarchia di ereditarietà

7

Sto lavorando al refactoring di un'applicazione legacy in cui ho implementato correttamente il pattern State come mostrato nello schema seguente:

Comevedic'èuncomportamentocomunetrai3stati,quindihodecisodiestrarreilmetodocomuneRefund()inunaclasseastrattagenitoreRefundableStatedovel'hoimplementata:

Refactoring ulteriormente noto che il metodo Ship() è comune tra i 2 stati Cancelled e NewOrder , quindi vado per un altro giro di estrazione del comportamento comune a un'altra classe genitore ShippableState :

Ora ho bisogno del mio stato Cancelled per estendere 2 classi e nello stesso tempo non posso raggruppare la funzione Refund() e Ship() insieme poiché non tutti gli stati permettono le 2 azioni.

Come posso risolvere questo problema?

Note:

  • Questa domanda riguarda principalmente l'applicazione di DRY e il refactoring non su come implementare il pattern di stato.
  • Sto usando PHP 5.3, ma sono aperto a qualsiasi altra lingua.
posta Songo 12.02.2013 - 10:31
fonte

4 risposte

2

Perché non mantenere le cose semplici? Puoi facilmente mantenere il tuo codice ASCIUTTO senza alcuna classe extra (il tuo primo progetto), e contrariamente alle altre risposte qui, IMHO non è necessario sacrificare il principio DRY per questo caso.

Se il metodo "Rimborso" è uguale in tutti e tre i casi, o il metodo "Spedisci" in soli due, implementa questi metodi in "OrderState". Se l'implementazione è solo simile, ma non identica, rifatta le differenze con i metodi di supporto virtuali che sono sovrascritti nelle sottoclassi (questo è chiamato metodo template Reticolo ). E se vuoi separare l'interfaccia dall'implementazione (che non ha nulla a che fare con "DRY", solo con "separazione delle preoccupazioni"), quindi sostituisci la classe OrderState con un'interfaccia IOrderState , e lascia che OrderState eredita da IOrderState , fornendo implementazioni predefinite e codice riutilizzabile per le altre sottoclassi.

Se la classe "Spedito" non ha bisogno di un'implementazione "Spedizione", inserisci la funzionalità comune in un nuovo metodo OrderState.DoShip e chiama quel metodo da Cancelled.Ship e NewOrder.Ship , ma non da Shipped.Ship (l'ultimo dovrebbe generare un'eccezione o non fare nulla, qualunque cosa sia appropriata nel tuo caso).

E se l'implementazione dei metodi "Refund" o "Ship" raggiunge una certa dimensione, e necessita di diversi metodi helper e forse alcune variabili di stato, allora è il momento di pensare a estrarre quei metodi per classi helper come "Refunder" o "Mittente" (che non ottiene parte della gerarchia ereditaria).

    
risposta data 12.02.2013 - 14:04
fonte
3

In Java (non so PHP), potresti creare ShippableState, RefundableState (e CancellableState) come interfacce, creare la classe ShippableRefundableState che implementa ShippableState e RefundableState. Ma che dire di CancellableState? Senza l'ereditarietà multipla dell'implementazione, non si può installare lì da qualche parte senza ripeterlo.

Quindi forse non usare l'ereditarietà, usa la composizione. Quindi hai StateShipper / Refunder / Canceller con il metodo XXXState (stato) e delegati ad esso nei tuoi stati.

In una lingua con più ereditarietà dell'implementazione (ad esempio C ++) è possibile creare classi anziché interfacce ed ereditarle da essi negli stati. Finché implementazioni multiple non moltiplicano l'implementazione della stessa cosa, è bello, le cose terribili iniziano quando non è chiaro (a umano) da quale antenato è qualcosa.

    
risposta data 12.02.2013 - 10:55
fonte
2

Posso solo dire che "ASCIUTTO" non è un proiettile d'argento. Devi usare il buon senso quando decidi se è necessario un refactoring specifico.

Nel tuo esempio puoi vedere chiaramente che ci sono problemi nell'applicare l'approccio 100% DRY. Penso che questo particolare refactoring in questo scenario non sia necessario. Soprattutto il 2 ° passaggio.

Ma se vuoi davvero farlo allora ...

Ti consiglierei di eliminare Design basato sull'ereditarietà e iniziare a utilizzare Design basato sull'interfaccia . Questo limite di ereditarietà è stato creato perché gli sviluppatori dovevano creare soluzioni "pazze" e ingestibili. Se incontri problemi di ereditarietà, stai chiaramente facendo qualcosa di sbagliato.

Con le interfacce puoi utilizzare la composizione e l'iniezione delle dipendenze per memorizzare codice simile in un unico posto.

    
risposta data 12.02.2013 - 10:59
fonte
2

Sebbene DRY sia un buon principio, puoi anche esagerare.

Anche se ciascuno degli stati supporta un metodo di rimborso, ciò non significa che tu stia ripetendo te stesso. Ciò avverrebbe solo se le regole aziendali stabiliscono che la gestione del rimborso deve essere identica in due o più stati.
Ma anche se c'è un comportamento comune tra un gruppo di stati, non vedo un vantaggio nell'aggiungere più classi intermedie. In definitiva, tutte le classi di stato nel pattern devono ereditare dallo stesso genitore, quindi il comportamento comune per diverse operazioni potrebbe anche essere raggruppato in quella classe base (specialmente se quel comportamento comune è indicare "operazione non supportata").

    
risposta data 12.02.2013 - 11:44
fonte

Leggi altre domande sui tag