Mappa delle funzioni e dell'istruzione switch

20

Sto lavorando a un progetto che elabora le richieste e ci sono due componenti alla richiesta: il comando e i parametri. Il gestore per ciascun comando è molto semplice (< 10 linee, spesso < 5). Ci sono almeno 20 comandi e probabilmente ne avranno più di 50.

Ho trovato un paio di soluzioni:

  • un grosso interruttore / se-altro sui comandi
  • mappa dei comandi alle funzioni
  • mappa di comandi a classi statiche / singleton

Ogni comando esegue un piccolo controllo degli errori e l'unico bit che può essere estratto è il controllo del numero di parametri, definito per ciascun comando.

Quale sarebbe la migliore soluzione a questo problema e perché? Sono anche aperto a tutti i modelli di design che potrei aver perso.

Ho trovato il seguente elenco di pro / contro per ciascuno:

interruttore

  • Pro
    • mantiene tutti i comandi in una funzione; dal momento che sono semplici, questo lo rende una tabella di ricerca visiva
    • non c'è bisogno di ingombrare fonti con tonnellate di piccole funzioni / classi che saranno utilizzate solo in un unico posto
  • cons
    • molto lungo
    • difficile aggiungere comandi a livello di codice (è necessario concatenare il caso predefinito)

comandi della mappa - > la funzione

  • Pro
    • pezzi piccoli, dimensioni morso
    • può aggiungere / rimuovere comandi a livello di codice
  • cons
    • se fatto in linea, lo stesso visivamente come interruttore
    • se non eseguito in linea, molte funzioni vengono utilizzate solo in un unico punto

comandi della mappa - > classe statica / singleton

  • Pro
    • può utilizzare il polimorfismo per gestire il semplice controllo degli errori (solo come 3 linee, ma comunque)
    • vantaggi simili alla mappa - > soluzione di funzione
  • cons
    • molte classi molto piccole ingombrano il progetto
    • L'implementazione
    • non è tutta nello stesso posto, quindi non è così semplice eseguire la scansione delle implementazioni

Note aggiuntive:

Sto scrivendo questo in Go, ma non penso che la soluzione sia specifica per la lingua. Sto cercando una soluzione più generale perché potrei dover fare qualcosa di molto simile in altre lingue.

Un comando è una stringa, ma posso facilmente mapparlo a un numero se conveniente. La firma della funzione è simile a:

Reply Command(List<String> params)

Go ha funzioni di primo livello e altre piattaforme che sto considerando hanno anche funzioni di livello superiore, quindi la differenza tra la seconda e la terza opzione.

    
posta beatgammit 03.04.2013 - 18:46
fonte

6 risposte

13

Si adatta perfettamente a una mappa (seconda o terza soluzione proposta). L'ho usato decine di volte, ed è semplice ed efficace. Non distinguo veramente tra queste soluzioni; il punto importante è che esiste una mappa con nomi di funzioni come i tasti.

Il principale vantaggio dell'approccio alla mappa, a mio parere, è che la tabella è dati Ciò significa che può essere passato in giro, aumentato o modificato in runtime; è anche facile codificare funzioni aggiuntive che interpretano la mappa in modi nuovi ed entusiasmanti. Ciò non sarebbe possibile con la soluzione case / switch.

Non ho mai sperimentato gli svantaggi che menzioni, ma vorrei menzionare un ulteriore svantaggio: l'invio è facile se conta solo il nome della stringa, ma se devi prendere in considerazione informazioni aggiuntive per decidere quale funzione da eseguire, è molto meno pulito.

Forse non mi sono mai imbattuto in un problema abbastanza difficile, ma vedo poco valore in entrambi i pattern di comando e codifico il dispatch come una gerarchia di classi, come altri hanno menzionato. Il nocciolo del problema è l'associazione delle richieste alle funzioni; una mappa è semplice, ovvia e facile da testare. Una gerarchia di classi richiede più codice e design, aumenta l'accoppiamento tra quel codice e può costringerti a prendere più decisioni in anticipo che sarà probabilmente necessario modificare in seguito. Non penso che lo schema di comando si applichi affatto.

    
risposta data 03.04.2013 - 21:56
fonte
4

Il tuo problema si presta molto bene al schema di progettazione del comando . Quindi in pratica avremo un'interfaccia Command di base e quindi ci sarebbero più classi CommandImpl che implementerebbero quell'interfaccia. L'interfaccia ha essenzialmente bisogno di un solo metodo doCommand(Args args) . Puoi passare gli argomenti tramite un'istanza di Args class. In questo modo sfrutti il potere del polimorfismo invece delle enunciate goffe se / else. Anche questo design è facilmente estensibile.

    
risposta data 03.04.2013 - 20:13
fonte
3

Ogni volta che mi trovo a chiedere se dovrei usare un'istruzione switch o un polimorfismo stile OO, mi riferisco al problema di espressione . Fondamentalmente, se hai "casi" diversi per i tuoi dati e bacchetta per supportare diverse "azioni" (dove ogni azione fa qualcosa di diverso per ogni caso), è davvero difficile creare un sistema che ti permetta di aggiungere sia nuovi casi che nuove azioni in futuro.

Se si usano le istruzioni switch (o il pattern Visitor), è facile aggiungere nuove azioni (perché tutto è racchiuso in una singola funzione) ma è difficile aggiungere nuovi casi (perché è necessario tornare indietro e modificare le vecchie funzioni)

Al contrario, se usi il polimorfismo stile OO è facile aggiungere nuovi casi (basta creare una nuova classe) ma è difficile aggiungere metodi a un'interfaccia (perché allora devi tornare indietro e modificare un gruppo di classi)

Nel tuo caso hai solo un metodo che devi supportare (processare una richiesta) ma molti casi possibili (ogni comando diverso). Poiché semplificare l'aggiunta di nuovi casi è più importante dell'aggiunta di nuovi metodi, è sufficiente creare una classe separata per ciascun comando diverso.

A proposito, dal punto di vista di come sono estensibili, non fa una grande differenza se si usano classi o funzioni. Se ci confrontiamo con una dichiarazione di switch, ciò che conta è il modo in cui le cose vengono "inviate" e entrambe le classi e le funzioni vengono inviate allo stesso modo. Quindi, usa semplicemente quello che è più conveniente nella tua lingua (e poiché Go ha scope e scope lessicali, la distinzione tra classi e funzioni è in realtà molto piccola).

Ad esempio, puoi solitamente utilizzare la delega per eseguire la parte di controllo degli errori anziché affidarti all'ereditarietà (il mio esempio è in Javascript perché O non so Sintassi Go, spero non ti dispiaccia)

function make_command(real_command){
    return function(x){
        if(check_arguments(x)){
            return real_command(x);
        }else{
            //handle error here
        }
    }
 }

 command1 = make_command(function(x){ 
     //do something
 })

 command2 = make_command(function(x){
     //do something else
 })

 command1(17);
 commnad2(42);

Naturalmente, questo esempio presuppone che esista un modo ragionevole per avere la funzione wrapper o gli argomenti di controllo della classe genitore per ogni caso. A seconda di come vanno le cose, potrebbe essere più semplice mettere la chiamata a check_arguments all'interno dei comandi stessi (poiché ogni comando potrebbe dover chiamare la funzione di controllo con argomenti diversi, a causa di un diverso numero di argomenti, diversi tipi di comando, ecc.)

tl; dr: non esiste il modo migliore per risolvere tutti i problemi. Dal punto di vista del "fare le cose in ordine", concentrati sulla creazione delle tue astrazioni in modo da imporre invarianti importanti e proteggerti dagli errori. Da una prospettiva "a prova di futuro", tieni presente quali parti del codice hanno maggiori probabilità di essere estese.

    
risposta data 07.04.2013 - 04:53
fonte
1

Non ho mai usato go, come programmatore c # probabilmente andrei su questa linea, speriamo che questa architettura si adatti a quello che stai facendo.

Creo una piccola classe / oggetto per ognuno con la funzione principale da eseguire, ognuno dovrebbe conoscere la sua rappresentazione di stringa. Questo ti dà la plug-in che suona come vuoi, poiché il numero di funzioni aumenterà. Nota che non userei la statica a meno che tu non ne abbia davvero bisogno, non danno molto vantaggio.

Avrei quindi una fabbrica che sa come scoprire queste classi in fase di esecuzione modificandole per essere caricate da diversi gruppi ecc. questo significa che non ne hai bisogno tutti nello stesso progetto.

Quindi rende anche più modulare il test e dovrebbe rendere il codice bello e piccolo, che è più facile da mantenere in seguito.

    
risposta data 03.04.2013 - 20:06
fonte
0

Come selezioni i tuoi comandi / funzioni?

Se c'è un modo "intelligente" di selezionare la funzione corretta, allora quella è la strada da percorrere - significa che è possibile aggiungere nuovo supporto (magari in una libreria esterna) senza modificare la logica di base.

Inoltre, testare le singole funzioni è più semplice rispetto a un'enorme istruzione switch.

Finalmente, usato solo in un posto - potresti scoprire che una volta arrivati a 50 è possibile riutilizzare diversi bit di diverse funzioni?

    
risposta data 03.04.2013 - 18:49
fonte
0

Non ho idea di come funziona Go, ma un'architettura che ho usato in ActionScript è quella di avere una lista Doubly Linked che funge da catena di responsabilità. Ogni link ha una funzione deterministica di responsabilità (che ho implementato come callback, ma potresti scrivere ogni link separatamente se questo funziona meglio per quello che stai cercando di fare). Se un collegamento determinava che aveva una responsabilità, chiamava meetResponsibility (di nuovo, una richiamata), e ciò interrompeva la catena. Se non avesse responsabilità, passerebbe la richiesta al prossimo link nella catena.

Diversi casi d'uso potrebbero essere facilmente aggiunti e rimossi semplicemente aggiungendo un nuovo collegamento tra i collegamenti di (o alla fine della) catena esistente.

Questo è simile a, ma sottilmente diverso da, la tua idea di una mappa di funzioni. La cosa bella è che hai appena passato la richiesta e fa la sua parte senza dover fare altro. Il lato negativo è che non funziona bene se è necessario restituire un valore.

    
risposta data 14.05.2013 - 01:51
fonte

Leggi altre domande sui tag