2012-06-17 10 views
11

Gracias a Microsoft por Intellisense y Atomineer por Atomineer Utils ... Todos estos parámetros son necesarios e inmutables.Mejore en un constructor de 13 parámetros

¿Hay una mejor manera de hacerlo?

/************************************************************************************************** 
* <summary>Initializes a new instance of the ADTBattleCharacter class.</summary> 
* <param name="name">   The name of the character.</param> 
* <param name="max_HP">  The maximum hit points.</param> 
* <param name="max_MP">  The maximum magic power.</param> 
* <param name="strength">  The strength.</param> 
* <param name="agility">  The agility.</param> 
* <param name="attack_power"> The attack power.</param> 
* <param name="defense_power">The defense power.</param> 
* <param name="gold">   The gold carried by the character.</param> 
* <param name="experience"> The experience the character is worth.</param> 
* <param name="stop_resist"> The character's resistance to stopspell.</param> 
* <param name="sleep_resist"> The character's resistance to sleep.</param> 
* <param name="hurt_resist"> The character's resistance to hurt/hurtmore.</param> 
* <param name="spell_list"> Available spells.</param> 
**************************************************************************************************/ 
ADTBattleCharacter(std::string name, unsigned char max_HP, unsigned char max_MP, 
        unsigned char strength, unsigned char agility, 
        unsigned char attack_power, unsigned char defense_power, 
        unsigned short gold, unsigned short experience, 
        double stop_resist, double sleep_resist, double hurt_resist, 
        std::bitset<SPELL_MAX> spell_list); 
+2

No puede empaquetarlos todos en algún objeto contenedor y pasarlos en su lugar? –

+1

Ponerlos en una estructura, dar valores predeterminados o basar algunos en otros, cambiar los que necesites y pasar la estructura? – chris

+0

@chris, que simplemente le muestra el problema al constructor de la estructura. Realmente no cambia nada. –

Respuesta

33

En cuanto a su caso específico, me parece que no ha roto las cosas muy bien.

Conceptualmente, un personaje en el sistema dispone de:

  • un nombre.
  • Un bloque de estadísticas, que contiene sus estadísticas básicas (HP, defensa, etc.).
  • Los atributos secundarios del personaje (experiencia).
  • Un inventario, que incluiría su lista actual de hechizos y su oro, entre otras cosas potencialmente.

Eso es 4 parámetros, no 13. Cuando está escribiendo una función, y ve que está tomando un gran número de parámetros, es probable que algunos de esos parámetros estén conceptualmente vinculados entre sí.Y las probabilidades también son buenas que otras funciones querrán utilizar esos parámetros vinculados.

Por ejemplo, es posible que desee mostrar el bloque de estadísticas de un personaje. ¿La función que hace esto realmente necesita el carácter? No; solo necesita el bloque de estadísticas. Entonces debería tomar un objeto de bloque de estadísticas.

Al igual que el constructor del personaje.

+4

+1 Como dijo Alan J Perlis: "Si tienes un procedimiento con diez parámetros, probablemente te hayas perdido algo". –

+0

Veo su punto, pero el refactor "Reemplazar parámetro con objeto" no alivia el problema, solo lo mueve a otra ubicación. Puede que solo sea 4 en este constructor, pero cuando esos 4 parámetros de clase se toman en cuenta, sigue siendo 13. – Casey

+2

@Casey: ¿Entonces? 13 parámetros repartidos en 4 clases es un promedio de 3,25 parámetros por constructor. ¿Hay algún problema con tener 4 parámetros para un constructor? ¿Considera una función de producto de puntos 4D vector para tomar 8 parámetros? No; toma 2, que resulta tener 4 valores en cada uno. De nuevo, no se trata solo de * esta * función; se trata de la organización adecuada de los datos. Un vector 4D es * un objeto *, sin importar cuántos parámetros tomen sus constructores. Así como un bloque de estadísticas es un solo objeto, no importa cuántas estadísticas contengan. –

0

Si el objetivo es solo reducir el número de argumentos suministrados al constructor, existen muchas formas de lograrlo. La verdadera pregunta es, como lo entiendo por los comentarios a mi primera publicación, es si hay una manera más fácil de administrar los parámetros.

Una forma de facilitar la administración de los parámetros es utilizar una estructura de datos general para mantenerlos. Algo así como un mapa.

enum AttrTag { AT_Name, AT_Max_HP, AT_Max_MP, //... 
       AT_Spells }; 

struct Attributes { 
    typedef std::unique_ptr<AttrBase> AttrPtr; 
    typedef std::map<AttrTag, AttrPtr> AttrMap; 
    AttrMap attributes; 

    template <AttrTag TAG> 
    typename Attr<TAG>::value_type get_attr() const { 
     AttrMap::const_iterator i = attributes.find(TAG); 
     if (i != attributes.end()) return i->second->attr_cast<TAG>()->value; 
     return Attr<TAG>::default_value; 
    } 

    template <AttrTag TAG> 
    void set_attr (typename Attr<TAG>::value_type value) { 
     attributes[TAG] = AttrPtr(new Attr<TAG>(value)); 
    } 

    bool has_attr (AttrTag t) const { 
     return attributes.find(t) != attributes.end(); 
    } 

}; 

y sería utilizado como esto:

Attributes attrs; 
attrs->set_attr<AT_Gold>(100); 
//... 
ADTBattleCharacter(attrs); 
//... 
unsigned short g = attrs->get_attr<AT_Gold>(); 

Los atributos que vienen fuera de la clase AttrBase que saber delegar al atributo real.

template <AttrTag> struct Attr; 

struct AttrBase { 
    virtual ~AttrBase() {} 
    template <AttrTag TAG> Attr<TAG> * attr_cast() { 
     return dynamic_cast<Attr<TAG> *>(this); 
    } 
}; 

y atributos se crearía a partir de una plantilla especializada Attr que hereda de AttrBase.

template <AttrTag TAG> 
struct Attr : public AttrBase { 
    typedef unsigned char value_type; 
    enum { default_value = 0 }; 
    value_type value; 
    Attr (value_type v) : value(v) {} 
}; 

template <> 
struct Attr<AT_Name> : public AttrBase { 
    typedef std::string value_type; 
    static std::string default_value; 
    value_type value; 
    Attr (value_type v) : value(v) {} 
}; 

template <> 
struct Attr<AT_Gold> : public AttrBase { 
    typedef unsigned short value_type; 
    enum { default_value = 1 }; 
    value_type value; 
    Attr (value_type v) : value(v) {} 
}; 

Esto permite que se agreguen nuevos atributos sin aumentar la complejidad de su constructor. Además, la misma colección de atributos podría pasarse a diferentes entidades, y cada uno podría reaccionar solo ante los atributos que le interesan. Solo se debe establecer un subconjunto de atributos. La presencia de un atributo se puede probar o se puede usar un valor predeterminado. Si desea agregar y eliminar atributos dinámicos, el contenedor podría extenderse para hacerlo agregando un mapa adicional para mantenerlos.

+0

Por favor comente en un voto a la baja, así que sé qué arreglar. ¡Gracias! – jxh

+2

-1: Esto es * mucho peor *. Al menos con el constructor con muchos parámetros, los parámetros tienen nombres razonables. Agrupar atributos por su tipo básico de C++ es funcionalmente * sin sentido *. Si vas a agruparlos, hazlo por algo que realmente signifique algo. –

+2

No hace nada para reducir la complejidad y lo hace más oscuro. Es mejor pasar un objeto 'config' o algo que encapsule los argumentos. – seand

1

La mejor manera es usar el Builder design pattern. O, más simplemente, puede declarar una clase que contiene campos para todos los parámetros a su constructor actual. La clase de parámetro puede tener un constructor (o constructores) que inicialice los campos a valores predeterminados razonables, y usted puede cambiar los valores accediendo directamente a los campos. Luego, implemente una función en la clase de parámetro para construir su objeto o defina un constructor de objeto que tome una instancia de la clase de parámetro.

Cuestiones relacionadas