2009-08-19 22 views
7

Creo que el problema es bastante común. Tiene una cadena de entrada y tiene que llamar a una función según el contenido de la cadena. Algo así como un interruptor() para cadenas. Piensa en las opciones de línea de comando.Buscando el código más elegante despachador

Actualmente estoy usando:

using std::string; 

void Myclass::dispatch(string cmd, string args) { 
    if (cmd == "foo") 
     cmd_foo(args); 
    else if (cmd == "bar") 
     cmd_bar(args); 
    else if ... 
     ... 
    else 
     cmd_default(args); 
} 

void Myclass::cmd_foo(string args) { 
... 
} 

void Myclass::cmd_bar(string args) { 
... 
} 

y en la cabecera

class Myclass { 
    void cmd_bar(string args); 
    void cmd_foo(string args); 
} 

Así que cada foo y bar tengo que repetir cuatro (4!) Veces. Sé que puedo alimentar los punteros de función y las cadenas a una matriz estática antes y hacer el envío en un bucle, guardando algunas líneas if ... else. ¿Pero hay algún truco de macro (o abuso de preprocesador, según el POV), que hace que sea posible definir de alguna manera la función y al mismo tiempo hacer que actualice la matriz automágicamente? Entonces, ¿tendría que escribir solo dos veces, o posiblemente una vez si se usa en línea?

Estoy buscando una solución en C o C++.

+4

Esta pregunta se ha hecho en varias ocasiones, y he respondido aquí http://stackoverflow.com/questions/659581/replace-giant-switch-statement-with-what –

+2

También está solicitando un esquema de registro de algún tipo para reduzca el esfuerzo en mantener el diccionario. – djna

+0

Lo es, pero no creo que haya uno que lo satisfaga. Podrías hacer algo tosco y feo con macros, pero es más problemático de lo que vale. –

Respuesta

1

La solución macro feo, que tipo de pedido. Tenga en cuenta que no se registra automáticamente, pero mantiene algunas cosas sincronizadas, y también causará errores de compilación si solo agrega a las asignaciones, y no la función en el archivo fuente.

Mappings.h:

// Note: no fileguard 
// The first is the text string of the command, 
// the second is the function to be called, 
// the third is the description. 
UGLY_SUCKER("foo", cmd_foo, "Utilize foo."); 
UGLY_SUCKER("bar", cmd_bar, "Turn on bar."); 

parser.h:

class Myclass { 
... 
protected: 
    // The command functions 
    #define UGLY_SUCKER(a, b, c) void b(args) 
    #include Mappings.h 
    #undef UGLY_SUCKER 
}; 

Parser.cpp:

void Myclass::dispatch(string cmd, string args) { 
    if (cmd == "") 
     // handle empty case 
#define UGLY_SUCKER(a, b, c) else if (cmd == a) b(args) 
#include Mappings.h 
#undef UGLY_SUCKER 
    else 
     cmd_default(args); 
} 

void Myclass::printOptions() { 
#define UGLY_SUCKER(a, b, c) std::cout << a << \t << c << std::endl 
#include Mappings.h 
#undef UGLY_SUCKER 
} 

void Myclass::cmd_foo(string args) { 
... 
} 
+0

Bueno, concedido, es feo, pero parece reducir las repeticiones a dos. Pero un #include en el medio del código es realmente feo. +1 – hirschhornsalz

+1

+1, por el mismo motivo. Además, suponiendo que cada comando "foo" da como resultado llamar a cmd_foo, con un poco de stringification también se puede evitar la repetición en los parámetros. –

+0

Sí, estoy pensando en las líneas del operador "pegar" para. – hirschhornsalz

8

Parece que usted está buscando el Algo Command pattern

así:

crear un mapa como éste

std::map<std::string, Command*> myMap; 

continuación, sólo tiene que utilizar su clave para ejecutar el comando como este. ...

std::map<std::string, Command*>::iterator it = myMap.find(str); 
if(it != myMap.end()) { 
    it->second->execute() 
} 

Para registrar sus comandos simplemente haga este

myMap["foo"] = new CommandFoo("someArgument"); 
myMap["bar"] = new CommandBar("anotherArgument"); 
+0

No. Probablemente sea engañoso que haya usado funciones llamadas "cmd_ *" pero podrían ser algo diferente, la necesidad de no representar comandos. Además, el despachador no necesita estar vinculado a un objeto. – hirschhornsalz

+2

@drhirsch. No tienen que ser comandos reales. Ese es solo el nombre del patrón. Básicamente es una forma de ejecutar un código predefinido basado en una entrada – Glen

+0

. Creo que la solución que sugieren Neil y Glen es la respuesta correcta. No incluye el autorregistro, pero no estoy seguro de que exista una forma clara de hacerlo en C++. En Java o C#, sería muy factible, sin embargo. –

2

as alternative to the Command pattern se puede construir una tabla hash de cadena - punteros>función:

typedef void (*cmd)(string); 
+1

Esta no es una alternativa real al _pattern_: es el mismo concepto, implementado de manera diferente. – xtofl

5

La solución básica, por mi enlace en el comentario cuestión, es para mapear una cadena a una llamada a función de algún tipo.

Para registrarse en realidad la cadena -> función de puntero/funtor par:

En primer lugar, tienen un producto único objeto despachador (choque de horror!). Llamémoslo TheDispatcher: es un contenedor para map<string,Func>, donde Func es su puntero de función o tipo de functor.

Entonces, tienen una clase de registro:

struct Register { 
    Register(comst string & s, Func f) { 
     TheDispatcher.Add(s, f); 
    } 
}; 

Ahora en sus unidades de compilación individuales se crea objetos estáticos (choque de horror!):

Register r1_("hello", DoSayHello); 

Estos objetos se crearán (suponiendo el código no está en una biblioteca estática) y se registrará automáticamente con TheDispatcher.

Y en tiempo de ejecución, busca cadenas en TheDispatcher y ejecuta la función/functor asociado.

1

Deberá al menos definir las funciones y agregarlas a algún registro. (Si van a ser funciones miembro no en línea de alguna clase, también deberá declararlas). Aparte de algún lenguaje específico de dominio que genere el código real (como cjhuitt's macro hackery), no veo forma de evitar mencionar estas funciones dos (o tres) veces

+0

Sí, creo que finalmente lo tengo en mi gruesa calavera. – hirschhornsalz

Cuestiones relacionadas