2013-05-21 7 views
5

Quando abbiamo una gerarchia di oggetti che è puramente un'eredità di semantica e non di comportamenti, allora inevitabilmente dobbiamo scrivere "instanceof" o "if/else" ovunque per eseguire il controllo del tipo di esecuzione.Quando "if else"/"instance of" sono inevitabili, come possiamo migliorare il design oltre all'utilizzo del pattern visitor?

E.g.

Se ho una gerarchia di oggetti che ha

Class Function 

Class Average extends Function 

Class Sum extends Function 

Class Max extends Function 

Se c'è un metodo chiamato calcolare() in queste classi, quindi non abbiamo problemi, possiamo solo prendere il vantaggio del polimorfismo e questo disegno soddisfa l'LSP.

Tuttavia, se per qualche ragione non vogliamo aggiungere questo metodo calculate() a questa gerarchia, questi oggetti sono oggetti stateless puramente semplici che rappresentano solo la semantica.

allora siamo costretti a scrivere il seguente codice ovunque:

if (function instanceof Average) 
//perform average 
else if(function instanceof Sum) 
//perform sum 
else if(function instanceof Max) 
//perform max 

Il codice di cui sopra indica una cattiva progettazione, perché si scrive il codice in tutto il mondo e questo disegno è fragile ed è difficile da cambiare in seguito. Suppongo che se le funzioni numeriche sono limitate e il calcolo della funzione si trovano in un singolo punto questo forse è ok dipende dalla complessità.

Quello che ho saputo finora è che per risolvere l'approccio di cui sopra, l'unico modo possibile è quello di implementare un modello di visitatore, c'è un altro modo per risolvere il progetto di cui sopra oltre all'utilizzo del modello di visitatore?

Un problema che posso vedere dal modello di visitatore è che il metodo di accettazione del modello di visitatore non restituisce valore, questo non è conveniente a volte se il metodo accept() non soddisfa completamente il requisito.

+0

per quanto riguarda il modello di strategia: http://en.wikipedia.org/wiki/Strategy_pattern? –

+0

non credo che la strategia funzioni, per favore controlla i miei commenti sotto – grumpynerd

+0

hmmm okay. Ma se quei blocchi if-elseif-else sono ovunque nel tuo progetto, potrebbe funzionare per ridefinire questo blocco in un metodo di utilità separato –

risposta

1

questi oggetti sono oggetti stateless puramente semplici rappresentano solo la semantica.

Sembra che tu voglia utilizzare le enumerazioni anziché gli oggetti normali.

Quindi è possibile utilizzare le dichiarazioni switch e fare in modo che il compilatore verifichi che siano stati gestiti tutti i casi.

enum Function { Average, Sum, Max } 
+0

A mio parere non risolve un problema poiché è ancora necessario eseguire il passaggio ed è altamente non scalabile. –

+0

@MichalBorek: senza alcun metodo sulle funzioni, come farai a evitare di cambiare? – Thilo

+0

È anche possibile aggiungere i metodi 'calculate' all'enumerazione. – OldCurmudgeon

4

Se si conosce ancora il tipo in fase di compilazione è possibile utilizzare una classe di supporto:

class Function { 
} 

class Average extends Function { 
} 

class Sum extends Function { 
} 

class Max extends Function { 
} 

class FunctionHelper { 
    public Number calculate(Average a) { 
    return null; 
    } 

    public Number calculate(Sum s) { 
    return null; 
    } 

    public Number calculate(Max a) { 
    return null; 
    } 

    public Number calculate(Function a) { 
    return null; 
    } 

} 

genere che saresti i metodi di supporto statico, ma non si è limitato a questo - ci sono alcuni cose piuttosto interessanti che puoi fare con più sapori di classe helper.

+1

Questo è un approccio un po 'pericoloso, poiché si basa sulla distribuzione in fase di compilazione. E qual è l'ultimo metodo (con Function) che farà altro che la stessa istanza di cui vogliamo evitare. – Thilo

+0

@Thilo - Non lo definirei pericoloso - ho notato che è necessario conoscere i tipi al momento della compilazione. L'ultima implementazione 'Function' è l'equivalente di un default, forse restituirà 0;'. – OldCurmudgeon

+0

Quindi questo restituirà 0: 'Funzione x = nuova Sum(); calcola (x); 'e questo non:' Sum x = new Sum(); calcolare (x); '. – Thilo

0

Ho appena imbattuto il Chain of Responsibilty pattern che potrebbe risolvere il problema:

Per questo creiamo un gestore di classe per ogni funzione a partire da una classe base comune

public abstract class FunctionHandler { 
    private FunctionHandler nexthandler = null; 

    protected abstract boolean isConditionMet(Function function); 

    protected abstract void calculate(Function function); 

    public void handleFunction(Function function) { 
     if(function == null) { 
      return; 
     } 

     if (isConditionMet(function)) { 
      calculate(function); 
     } else { 
      if (nexthandler != null) { 
       nexthandler.handleFunction(function); 
      } 
     } 
    } 

    public FunctionHandler setNexthandler(FunctionHandler nexthandler) { 
     this.nexthandler = nexthandler; 
     return nexthandler; 
    } 
} 

Poi creiamo i gestori di cemento:

public class Averagehandler extends FunctionHandler { 
    @Override 
    protected boolean isConditionMet(Function function) { 
     return function instanceof Average; 
    } 

    @Override 
    protected void calculate(Function function) { 
     // do average stuff 
    } 
} 

public class SumHandler extends FunctionHandler { 
    @Override 
    protected boolean isConditionMet(Function function) { 
     return function instanceof Sum; 
    } 

    @Override 
    protected void calculate(Function function) { 
     // do sum stuff 
    } 
} 

public class MaxHandler extends FunctionHandler { 
    @Override 
    protected boolean isConditionMet(Function function) { 
     return function instanceof Max; 
    } 

    @Override 
    protected void calculate(Function function) { 
     // do max stuff 
    } 
} 

ora cerca un posto centrale bello mettere insieme la vostra catena (ad esempio una classe di utilità)

public final class FunctionUtil { 
    public static final FunctionHandler HANDLER; 

    static { 
     HANDLER = new Averagehandler(); 
     HANDLER.setNexthandler(new SumHandler()).setNexthandler(new MaxHandler()); 
    } 
} 

ora si può iniziare a sostituire i tuoi if-else-blocchi con le chiamate a FunctionUtil.HANDLER.handleFunction(function);

+0

grazie, Macro, tuttavia, ho appena visto il tuo suggerimento, non riuscivo a capire perché abbiamo bisogno di introdurre la catena di responsabilità per questo problema? Non sta semplicemente usando ogni oggetto del gestore per gestire abbastanza ogni funzione? Per esempio. Stavo pensando che sarebbe stato qualcosa del genere se usando questo tipo di approccio – grumpynerd

+0

un singolo gestore sarebbe stato sufficiente. la catena di responsabilità ti libera dalla decisione che il gestore è il giusto per l'oggetto 'Function' a portata di mano. prende semplicemente l'oggetto e lo passa ai gestori, a meno che non si abbia il diritto di gestire il tipo effettivo di funzione –