Esiste un modo più intelligente per fare ciò oltre a una lunga catena di istruzioni if o switch?

20

Sto implementando un bot IRC che riceve un messaggio e sto controllando quel messaggio per determinare quali funzioni chiamare. C'è un modo più intelligente di farlo? Sembra che sarebbe presto sfuggito di mano dopo che mi sono alzato a come 20 comandi.

Forse c'è un modo migliore per astrarre questo?

 public void onMessage(String channel, String sender, String login, String hostname, String message){

        if (message.equalsIgnoreCase(".np")){
//            TODO: Use Last.fm API to find the now playing
        } else if (message.toLowerCase().startsWith(".register")) {
                cmd.registerLastNick(channel, sender, message);
        } else if (message.toLowerCase().startsWith("give us a countdown")) {
                cmd.countdown(channel, message);
        } else if (message.toLowerCase().startsWith("remember am routine")) {
                cmd.updateAmRoutine(channel, message, sender);
        }
    }
    
posta Harrison Nguyen 13.06.2014 - 08:49
fonte

7 risposte

45

Utilizza una tabella di spedizione . Questa è una tabella contenente coppie ("parte del messaggio", pointer-to-function ). Il dispatcher avrà quindi questo aspetto (in pseudo codice):

for each (row in dispatchTable)
{
    if(message.toLowerCase().startsWith(row.messagePart))
    {
         row.theFunction(message);
         break;
    }
}

(il equalsIgnoreCase può essere gestito come caso speciale da qualche parte prima, o se hai molti di questi test, con una seconda tabella di distribuzione).

Ovviamente, ciò che pointer-to-function deve avere dipende dal tuo linguaggio di programmazione. Qui è un esempio in C o C ++. In Java o C # probabilmente userete espressioni lambda a tale scopo, o simulate "pointer-to-functions" usando il pattern di comando. Il libro online gratuito " Perl ordine superiore " ha un capitolo completo sulle tabelle di invio che utilizzano Perl.

    
risposta data 13.06.2014 - 09:26
fonte
31

Probabilmente farei qualcosa del genere:

public interface Command {
  boolean matches(String message);

  void execute(String channel, String sender, String login,
               String hostname, String message);
}

Quindi puoi avere ogni comando per implementare questa interfaccia e restituire true quando corrisponde al messaggio.

List<Command> activeCommands = new ArrayList<>();
activeCommands.add(new LastFMCommand());
activeCommands.add(new RegisterLastNickCommand());
// etc.

for (Command command : activeCommands) {
    if (command.matches(message)) {
        command.execute(channel, sender, login, hostname, message);
        break; // handle the first matching command only
    }
}
    
risposta data 13.06.2014 - 09:29
fonte
15

Stai usando Java - quindi rendilo bello; -)

Probabilmente lo farei usando Annotazioni:

  1. Crea un metodo personalizzato di annotazione

    @IRCCommand( String command, boolean perfectmatch = false )
    
  2. Aggiungi l'annotazione a tutti i metodi pertinenti nella classe, ad es.

    @IRCCommand( command = ".np", perfectmatch = true )
    doNP( ... )
    
  3. Nel tuo costruttore usa Reflections per creare una HashMap dei metodi da tutti i metodi annotati nella tua classe:

    ...
    for (Method m : getDeclaredMethods()) {
    if ( isAnnotationPresent... ) {
        commandList.put(m.getAnnotation(...), m);
        ...
    
  4. Nel tuo metodo onMessage , esegui semplicemente un ciclo su commandList cercando di far corrispondere la stringa su ognuno e chiamando method.invoke() dove si adatta.

    for ( @IRCCommand a : commanMap.keyList() ) {
        if ( cmd.equalsIgnoreCase( a.command )
             || ( cmd.startsWith( a.command ) && !a.perfectMatch ) {
            commandMap.get( a ).invoke( this, cmd );
    
risposta data 13.06.2014 - 11:06
fonte
5

Che cosa succede se definisci un'interfaccia, ad esempio IChatBehaviour che ha un metodo chiamato Execute che contiene un message e un cmd oggetto:

public Interface IChatBehaviour
{
    public void execute(String message, CMD cmd);
}

Nel tuo codice, quindi implementa questa interfaccia e definisci i comportamenti che desideri:

public class RegisterLastNick implements IChatBehaviour
{
    public void execute(String message, CMD cmd)
    {
        if (message.toLowerCase().startsWith(".register"))
        {
            cmd.registerLastNick(channel, sender, message);
        }
    }
}

E così via per il resto.

Nella tua classe principale, hai quindi un elenco di comportamenti ( List<IChatBehaviour> ) implementati dal tuo bot IRC. Potresti quindi sostituire le tue dichiarazioni if con qualcosa di simile a questo:

for(IChatBehaviour behaviour : this.behaviours)
{
    behaviour.execute(message, cmd);
}

Quanto sopra dovrebbe ridurre la quantità di codice che hai. L'approccio sopra descritto ti permetterà anche di fornire comportamenti aggiuntivi alla tua classe di bot senza modificare la classe del bot stesso (come per Strategy Design Pattern ).

Se vuoi attivare un solo comportamento in qualsiasi momento, puoi cambiare la firma del metodo execute per ottenere true (il comportamento è stato attivato) o false (il comportamento non ha sparato) e sostituisci il ciclo precedente con qualcosa di simile a questo:

for(IChatBehaviour behaviour : this.behaviours)
{
    if(behaviour.execute(message, cmd))
    { 
         break;
    }
}

Quanto sopra sarebbe più noioso da implementare e inizializzare poiché è necessario creare e passare tutte le classi extra, tuttavia, dovrebbe rendere il tuo bot facilmente estensibile e modificabile poiché tutte le classi di comportamento saranno incapsulate e si spera indipendenti l'uno dall'altro.

    
risposta data 13.06.2014 - 09:11
fonte
1

"Intelligente" può essere (almeno) tre cose:

Prestazioni più elevate

La proposta di Dispatch Table (e dei suoi equivalenti) è buona. Un tale tavolo si chiamava "CADET" negli anni passati per "Impossibile aggiungere, non provare nemmeno". Tuttavia, considera un commento per aiutare un manutentore inesperto su come gestire la tabella.

Maintainability

"Rendi bello" non è un'ammonizione inutile.

e, spesso trascurato ...

Resilienza

L'uso di toLowerCase ha delle insidie in quanto un testo in alcune lingue deve subire una ristrutturazione dolorosa quando si cambia tra magiscule e miniscule. Sfortunatamente, esistono le stesse insidie per toUpperCase. Basta essere consapevoli.

    
risposta data 14.06.2014 - 17:22
fonte
0

Potresti avere tutti i comandi implementare la stessa interfaccia. Quindi un parser di messaggi potrebbe restituirti il comando appropriato che eseguirai solo.

public interface Command {
    public void execute(String channel, String message, String sender) throws Exception;
}

public class MessageParser {
    public Command parseCommandFromMessage(String message) {
        // TODO Put your if/switch or something more clever here
        // e.g. return new CountdownCommand();
    }
}

public class Whatever {
    public void onMessage(String channel, String sender, String login, String hostname, String message) {
        Command c = new MessageParser().parseCommandFromMessage(message);
        c.execute(channel, message, sender);
    }
}

Sembra solo più codice. Sì, è ancora necessario analizzare il messaggio per sapere quale comando eseguire, ma ora si trova in un punto ben definito. Potrebbe essere riutilizzato altrove. (Potrebbe essere utile iniettare MessageParser, ma questa è un'altra questione. Inoltre, il modello di peso vivo potrebbe essere una buona idea per i comandi , a seconda di quanti ti aspetti di essere creato.)

    
risposta data 13.06.2014 - 09:08
fonte
0

Quello che vorrei fare è questo:

  1. Raggruppa i comandi che hai in gruppi. (ne hai almeno 20 in questo momento)
  2. Al primo livello, categorizza per gruppo, quindi hai il comando relativo al nome utente, i comandi dei brani, i comandi di conteggio, ecc.
  3. Quindi vai nel metodo di ogni gruppo, questa volta otterrai il comando originale.

Questo renderà questo più gestibile. Più vantaggio quando il numero di "else if" aumenta troppo.

Naturalmente, a volte avere questi "se non altro" non sarebbe un grosso problema. Non penso che il 20 sia così male.

    
risposta data 13.06.2014 - 12:47
fonte

Leggi altre domande sui tag