2011-10-18 13 views
7

Estoy refaccionando una sola clase de línea 3000 + con una red enmarañada de condicionales e interruptores en un conjunto de clases de trabajadores. Anteriormente parte de la constructora sería seleccionar qué "tipo" de que hay que utilizar a través de un código como el siguiente:¿Cómo seleccionar aleatoriamente una clase para instanciar sin usar el interruptor?

enum Type { FOO, BAR, BAZ }; 

Type choices[] = { FOO, FOO, BAR, BAZ }; // weighted towards FOO 
m_type = choices[rand()%4]; 

[...later...] 

void Run() { 
    switch (m_type) { 
     case FOO: do_foo(); break; 
     case BAR: do_bar(); break; 
     case BAZ: do_baz(); break; 
    } 
} 

Después de refactorización tengo separados TypeFoo, TypeBar y TypeBaz clases que cada uno tiene sus propias Run() métodos para hacer su trabajo . Lamentablemente, es complicado el código de selección de clase. No sé de ninguna manera de mantener una lista de posibles clases de construir, así que tengo esto:

Type *m_type; 

switch (mrand()%4) { 
    case 0: case 1: m_type = new TypeFoo(); break; 
    case 1:   m_type = new TypeBar(); break; 
    case 2:   m_type = new TypeBaz(); break; 
} 

Esto es todavía vale la pena el cambio porque el código de inicialización no se llama con regularidad, pero su momento más difícil de modifique esta lista, cambie las ponderaciones, etc.

¿Es relativamente sencillo lograr la claridad del código original?

+1

+1: Interesante pregunta ... :-) hacer todas esas clases heredan de una sola clase base? –

+0

Esto sería un poco mejor: 'interruptor (mrand()% 4) { caso BAR: t = new TypeBar(); descanso; case BAZ: t = new TypeBaz(); descanso; predeterminado: t = new TypeFoo(); descanso; // ponderado hacia Foo } ' – Yourpalal

+0

¿Por qué está etiquetado como' plantilla', cuando la selección tiene que suceder en el tiempo de ejecución? – iammilind

Respuesta

14

La respuesta es: una clase base y una matriz de indicadores de funciones pueden ayudarlo a hacerlo.

struct Base { virtual ~Base() {} }; //make ~Base() virtual 
struct Foo : Base {}; 
struct Bar : Base {}; 
struct Baz : Base {}; 

template<typename T> 
Base *Create() { return new T(); } 

typedef Base* (*CreateFn)(); 

CreateFn create[] = 
     { 
       &Create<Foo>, 
       &Create<Foo>, // weighted towards FOO 
       &Create<Bar>, 
       &Create<Baz> 
     }; 
const size_t fncount = sizeof(create)/sizeof(*create); 

Base *Create() 
{ 
    return create[rand() % fncount](); //forward the call 
} 

luego usarlo como (ideone demo):

int main() { 
     Base *obj = Create(); 
     //work with obj using the common interface in Base 

     delete obj; //ok, 
        //the virtual ~Base() lets you do it 
        //in a well-defined way 
     return 0; 
} 
+0

Tal vez quiera agregar un destructor virtual en Base ya que está sosteniendo un objeto de clase derivado a través de un puntero de clase base. –

+0

+1: Ese era el tipo de cosas que tenía en mente. Me lo ganaste con un ejemplo de código :-) –

+0

@EricZ: Lo he hecho hace mucho tiempo. – Nawaz

2

Sugeriría crear una clase base común (si aún no la tiene) y luego usar a factory class para encapsular el proceso de creación. La fábrica simplemente devolvería un puntero a su clase base que tiene el método de ejecución del prototipo.

Algo a lo largo de estas líneas:

class Type 
{ 
    virtual void Run() = 0; 
}; 

class TypeFoo : public Type 
{ 
public: 
    TypeFoo() {}; 
    virtual void Run() {}; 
    static Type* Create() { return new TypeFoo(); }; 
}; 

class TypeBar : public Type 
{ 
public: 
    TypeBar() {}; 
    virtual void Run() {}; 
    static Type* Create() { return new TypeBar(); }; 
}; 

class TypeBaz : public Type 
{ 
public: 
    TypeBaz() {}; 
    virtual void Run() {}; 
    static Type* Create() { return new TypeBaz(); }; 
}; 

class TypeFactory 
{ 
    typedef Type* (*CreateFn)(); 

public: 
    static Type* RandomTypeFooWeighted() 
    { 
     CreateFn create[] = 
     { 
      TypeFoo::Create, 
      TypeFoo::Create, // weighted towards FOO 
      TypeBar::Create, 
      TypeBaz::Create 
     }; 
     const int fncount = sizeof(create)/sizeof(*create); 
     return create[ rand()%fncount ](); 
    } 
}; 

Así que lo utilizan sólo se puede llamar:

Type *t = TypeFactory::RandomTypeFooWeighted(); 

crédito a Nawaz para los bits de puntero de función y sacudidas.

Cuestiones relacionadas