Vantaggi del modello di strategia


15

Perché è utile utilizzare il modello di strategia se puoi semplicemente scrivere il tuo codice in casi if / then?

Ad esempio: ho una classe TaxPayer e uno dei suoi metodi calcola le tasse utilizzando algoritmi diversi. Quindi perché non può avere casi if / then e capire quale algoritmo usare in quel metodo, invece di usare il modello di strategia? Inoltre, perché non puoi semplicemente implementare un metodo separato per ciascun algoritmo nella classe TaxPayer?

Inoltre, cosa significa che l'algoritmo cambia in fase di esecuzione?


2
Sono compiti? In tal caso è meglio dichiararlo in anticipo.
Fuhrmanator,

2
@Fuhrmanator no isnt
Armon Safai

Risposte:


20

Per prima cosa, grossi if/elseblocchi di blocchi non sono facilmente verificabili . Ogni nuovo "ramo" aggiunge un altro percorso di esecuzione e quindi aumenta la complessità ciclomatica . Se vuoi testare a fondo il tuo codice, dovresti coprire tutti i percorsi di esecuzione e ogni condizione richiederebbe di scrivere almeno un altro test (supponendo che tu scriva piccoli test focalizzati). D'altra parte, le classi che implementano strategie in genere espongono solo 1 metodo pubblico, che è facile da testare.

Quindi, con nidificati if/elsefinirai con molti test per una singola parte del tuo codice, mentre con Strategia avrai pochi test per ognuna delle molteplici strategie più semplici. Con quest'ultimo, è facile avere una migliore copertura, perché è più difficile perdere i percorsi di esecuzione.

Per quanto riguarda l' estensibilità , immagina di scrivere un framework in cui gli utenti dovrebbero poter iniettare il proprio comportamento. Ad esempio, si desidera creare una sorta di quadro di calcolo delle imposte e si desidera supportare i sistemi fiscali di diversi paesi. Invece di implementarli tutti, vuoi solo dare agli utenti del framework la possibilità di fornire un'implementazione di come calcolare alcune tasse particolari.

Ecco il modello di strategia:

  • Definisci un'interfaccia, ad esempio TaxCalculation, e il tuo framework accetta istanze di questo tipo per calcolare le tasse
  • Un utente del framework crea una classe che implementa questa interfaccia e la passa al framework, fornendo così un modo per eseguire parte dei calcoli

Non puoi fare lo stesso con if/else, perché ciò richiederebbe la modifica del codice del framework, nel qual caso non sarebbe più un framework. Poiché i framework sono spesso distribuiti in forma compilata, questa potrebbe essere l'unica opzione.

Tuttavia, anche se scrivi solo un codice normale, la strategia è utile perché rende più chiari i tuoi intenti. Dice "questa logica è collegabile e condizionale", cioè possono esserci più implementazioni che possono variare a seconda delle azioni dell'utente, della configurazione o persino della piattaforma.

Utilizzando il modello di strategia può migliorare la leggibilità , perché, mentre una classe che implementa qualche strategia particolare in genere dovrebbe avere un nome descrittivo, ad esempio USAIncomeTaxCalculator, if/elsei blocchi sono "senza nome", in casi migliori solo commentato, e commenti possono mentire. Inoltre, per i miei gusti personali, avere solo più di 3 if/elseblocchi di fila non è leggibile, e diventa piuttosto male con i blocchi nidificati.

Anche il principio Open / Closed è molto rilevante, perché, come ho descritto nell'esempio sopra, la strategia consente di estendere una logica in alcune parti del codice ("apri per estensione") senza riscrivere quelle parti ("chiuso per modifica" ).


1
if/elseblocca anche la leggibilità del codice inferiore. Per quanto riguarda il modello di strategia, vale la pena menzionare il principio Open / Closed dell'IMO.
Maciej Chałapuk,

1
La testabilità è la ragione principale. (La maggior parte) Ogni ramo nel tuo codice dovrebbe essere testato. Più ifs hai, più percorsi possibili esistono attraverso il tuo codice, più test devi scrivere e più modi per far fallire quel metodo. Se posso citare il compianto Yogi Berra: "Se vieni a un bivio, prendilo." Questo si applica brillantemente ai test unitari. Inoltre, molte ifaffermazioni indicano che è probabile che ripeterai la logica per tali condizioni, aumentando ulteriormente il carico di prova e aumentando il rischio di errori.
Greg Burghardt,

grazie per la risposta. Quindi perché non posso usare metodi separati per algoritmi diversi nella stessa classe?
Armon Safai,

Puoi, ma avresti comunque bisogno di un sacco di if/elseblocchi per chiamarli (o al loro interno, per determinare se dovrebbe fare qualcosa o meno), quindi non è di grande aiuto, tranne forse un codice più leggibile. E quindi anche nessuna estensibilità per gli utenti del tuo quadro ipotetico.
scriptin

1
Puoi essere più chiaro sul perché è più facile testarlo? L'esempio per il refactoring di un'istruzione case (o if / then) in un metodo polimorfico (base per la strategia) è piuttosto facile da testare. refactoring.com/catalog/replaceConditionalWithPolymorphism.html Se conosco tutte le condizioni per testare, scrivo un test per ognuna. Se ho delle strategie, devo creare un'istanza ed eseguirne una per ciascuna. Come è più semplice testare l'approccio strategico? Non stiamo parlando di if nidificati complessi quando si fa riferimento alla strategia.
Fuhrmanator,

5

Perché è utile utilizzare il modello di strategia se puoi semplicemente scrivere il tuo codice in casi if / then?

A volte dovresti semplicemente usare if / then. È un codice semplice e facile da leggere.

I due problemi chiave con il semplice codice if / then è che può violare il principio aperto chiuso . Se devi mai entrare e aggiungere o modificare una condizione, stai modificando questo codice. Se ti aspetti di avere più condizioni, semplicemente aggiungere una nuova strategia è più semplice / più pulito / meno probabile che si rompa.

L'altro problema è l'accoppiamento. Usando if / then, tutte le implementazioni sono legate a tale implementazione, rendendole più difficili da cambiare in futuro. Usando la strategia, l'unico accoppiamento è all'interfaccia della strategia.


cosa c'è di sbagliato nella modifica del codice nel codice if / then? non dovresti modificare anche il codice nel modello di strategia se decidi di cambiare il funzionamento di uno degli algoritmi?
Armon Safai,

@armonsafai: se modifichi la strategia, devi solo testare la strategia. Se si modificano tutti gli algoritmi, è necessario testare tutti gli algoritmi. Peggio ancora, se aggiungi una nuova strategia, devi solo testare la strategia. Se aggiungi un nuovo condizionale, devi testare tutti i condizionali.
Telastyn,

4

La strategia è utile quando le if/thencondizioni si basano sui tipi , come spiegato in http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html

I condizionali di controllo del tipo di solito non hanno un'elevata complessità ciclomatica, quindi non direi che la strategia migliora necessariamente le cose lì.

Il motivo principale della strategia è spiegato nel libro GoF p.316 che ha introdotto il modello:

Utilizzare il modello di strategia quando

...

  • una classe definisce molti comportamenti e questi compaiono come più istruzioni condizionali nelle sue operazioni. Invece di molti condizionali, sposta i relativi rami condizionali nella propria classe Strategia.

Come menzionato in altre risposte, se applicato in modo appropriato il modello di strategia consente di aggiungere nuove estensioni (strategie concrete) senza necessariamente modificare il resto del codice. Questo è il cosiddetto principio Open-Closed o Principio delle variazioni protette . Ovviamente, devi ancora codificare la nuova strategia concreta e il codice client deve riconoscere le strategie come plug-in (questo non è banale).

Con i if/thencondizionali, è necessario modificare il codice della classe contenente la logica condizionale. Come menzionato in altre risposte, a volte è OK quando non si desidera aggiungere la complessità per supportare l'aggiunta di nuove funzionalità (plug-in) senza ricompilare.


3

[...] se riesci a scrivere il tuo codice in caso di if / then?

Questo è esattamente il più grande vantaggio del modello strategico. Non avendo condizioni.

Vuoi che le tue classi / metodi / funzioni siano le più semplici e brevi possibile. Il codice funzione è molto facile da testare e molto facile da leggere.

Le condizioni ( if/ elseif/ else) rendono lunghe le tue classi / metodi / funzioni, perché di solito il codice in cui una decisione valuta trueè diverso dalla parte in cui la decisione valuta false.


Un altro grande vantaggio del modello di strategia è che è riutilizzabile in tutto il progetto.

Quando si utilizza il modello di progettazione della strategia, è molto probabile che si disponga di un tipo di contenitore IoC, dal quale si ottiene l'implementazione desiderata di un'interfaccia, magari mediante un getById(int id)metodo, in cui idpotrebbe essere un membro enumeratore.

Ciò significa che la creazione dell'implementazione è solo in un punto del codice.

Se desideri aggiungere più implementazioni, aggiungi la nuova implementazione al getByIdmetodo e questa modifica si riflette ovunque nel codice in cui la chiami.

Con if/ elseif/ elsequesto è impossibile da fare. Aggiungendo una nuova implementazione, devi aggiungere un nuovo elseifblocco e farlo ovunque dove sono state utilizzate le implementazioni, oppure potresti finire con un codice che non è valido, perché hai dimenticato di aggiungere l'implementazione alla sua struttura.


Inoltre, cosa significa che l'algoritmo cambia in fase di esecuzione?

Nel mio esempio, idpotrebbe essere una variabile che viene popolata in base a un input dell'utente. Se l'utente fa clic su un pulsante A, quindi id = 2, se fa clic su un pulsante B, quindi id = 8.

A causa del diverso idvalore, si ottiene un'implementazione diversa di un'interfaccia dal contenitore IoC e il codice esegue diverse operazioni.


grazie per la risposta. Quindi perché non posso usare metodi separati per algoritmi diversi nella stessa classe?
Armon Safai,

@ArmonSafai Metodi separati risolverebbero davvero qualsiasi cosa? Io non la penso così. Stai spostando il problema da un luogo a un altro e la decisione su quale metodo chiamare verrà presa in base al risultato di una condizione. Ancora una volta, uno if/ elseif/ elsestate. Come prima, solo in un posto diverso.
Andy,

Quindi i casi if / then sarebbero in linea di principio? Non dovresti usare i casi if / in main anche per il modello di strategia?
Armon Safai,

1
@ArmonSafai No, non lo faresti. Avresti un interruttore sulla idvariabile nel getByIdmetodo, che restituirebbe l'implementazione specifica. Ogni volta che avresti bisogno di un'implementazione dell'interfaccia, chiederesti al contenitore IoC di consegnartelo.
Andy,

1
@ArmonSafai Potresti anche avere un metodo che getSortByEnumType(SortEnum type)restituisce un'implementazione di Sortun'interfaccia, un metodo che getSortTyperestituisca una SortEnumvariabile e prenda una raccolta come parametro, e il getSortByEnumTypemetodo conterrebbe di nuovo un interruttore sul typeparametro che ti restituirà l'algoritmo di ordinamento corretto. Se è necessario aggiungere un nuovo algoritmo di ordinamento, è sufficiente modificare l'enum e un metodo. E sei pronto.
Andy,

2

Perché è utile utilizzare il modello di strategia se puoi semplicemente scrivere il tuo codice in casi if / then?

Il modello di strategia ti consente di separare i tuoi algoritmi (i dettagli) dalla tua logica aziendale (politica di alto livello). Queste due cose non sono solo confuse da leggere se mescolate, ma hanno anche ragioni molto diverse per cambiare.

C'è anche un importante fattore di scalabilità del lavoro di squadra qui. Immagina un grande team di programmazione in cui molte persone stanno lavorando a questo pacchetto di contabilità. Se gli algoritmi fiscali sono tutti nella classe o modulo TaxPayer , allora diventano probabili conflitti di unione. Unire i conflitti è dispendioso in termini di tempo e soggetto a errori. Questa volta prosciuga la produttività del team e gli errori introdotti da cattive fusioni danno credibilità ai clienti.

Inoltre, cosa significa che l'algoritmo cambia in fase di esecuzione?

Un algoritmo che cambia in fase di esecuzione è uno il cui comportamento è determinato dalla configurazione o dal contesto. Un approccio if / then in atto non lo abilita efficacemente in quanto comporta il ricaricamento di classi utilizzate attivamente esistenti. Con il modello di strategia gli oggetti di strategia che implementano ciascun algoritmo possono essere costruiti sull'uso. Di conseguenza le modifiche a questi algoritmi (correzioni di bug o miglioramenti) potrebbero essere apportate e ricaricate in fase di esecuzione. Questo approccio potrebbe essere utilizzato per consentire la disponibilità continua e zero tempi di inattività.


1

Non c'è nulla di sbagliato in if/elsesé. In molti casi if/elseè il modo più semplice e più leggibile di esprimere la logica. Quindi l'approccio che descrivi è perfettamente valido in molti casi. (È anche perfettamente testabile, quindi non è un problema.)

Ma ci sono alcuni casi particolari in cui un modello di strategia può migliorare la manutenibilità dell'intero codice. Per esempio:

  • Se i particolari algoritmi di calcolo fiscale possono cambiare indipendentemente l'uno dall'altro e dalla logica di base. In questo caso sarebbe bello averli separati in classi distinte, poiché le modifiche saranno localizzate.
  • Se in futuro potrebbero essere aggiunti nuovi algoritmi, senza cambiare la logica di base.
  • Se la causa della differenza tra i due algoritmi influisce anche su altre parti del codice. Supponi di selezionare tra i due algoritmi in base alla fascia di reddito del contribuente. Se questa fascia di reddito causa anche la selezione di diverse filiali in altri punti del codice, è più semplice creare un'istanza di una strategia corrispondente alla fascia di reddito una volta e chiamare quando necessario, anziché disporre di più rami if / else sparsi sul codice.

Affinché il modello di strategia abbia senso, l'interfaccia tra la logica di base e gli algoritmi di calcolo delle imposte dovrebbe essere più stabile rispetto ai singoli componenti. Se è altrettanto probabile che una modifica dei requisiti causerà la modifica dell'interfaccia, il modello di strategia potrebbe effettivamente essere una responsabilità.

Tutto dipende dal fatto che gli "algoritmi di calcolo fiscale" possano essere separati in modo netto dalla logica di base che lo invoca. Un modello di strategia ha un certo sovraccarico rispetto a un if/else, quindi dovrai decidere caso per caso se l'investimento vale la pena.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.