Улучшить конструктор с 13 параметрами

Благодарим Microsoft за Intellisense и Atomineer за Atomineer Utils... Все эти параметры требуются и неизменяемы.

Есть ли лучший способ сделать это?

/**************************************************************************************************
 * <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 resistance to stopspell.</param>
 * <param name="sleep_resist"> The character resistance to sleep.</param>
 * <param name="hurt_resist">  The character 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);

Ответ 1

Посмотрев на ваш конкретный случай, мне кажется, что вы не очень хорошо разобрались.

Понятно, что персонаж вашей системы:

  • Имя.
  • Блок stat, содержащий их базовую статистику (HP, защита и т.д.).
  • Вторичные атрибуты символов (опыт).
  • Реестр, который будет включать их текущий список заклинаний и их золото, среди потенциально других вещей.

Это 4 параметра, а не 13. Когда вы пишете функцию, и видите, что она принимает большое количество параметров, вероятность хороша тем, что некоторые из этих параметров концептуально связаны друг с другом. И шансы также хороши, что другие функции захотят использовать эти связанные параметры.

Например, вы можете захотеть отобразить блок stat. Нужна ли функция, которая действительно нуждается в этом персонаже? Нет; ему просто нужен блок stat. Поэтому он должен принимать объект блока stat.

Так же, как конструктор символов.

Ответ 2

Лучше всего использовать шаблон дизайна Builder. Или, проще говоря, вы можете объявить класс, который содержит поля для всех параметров для вашего текущего конструктора. Класс параметров может сам иметь конструктор (или конструкторы), который инициализирует поля разумными значениями по умолчанию, и вы меняете значения, напрямую обращаясь к полям. Затем либо реализуйте функцию в классе параметров для построения вашего объекта, либо определите конструктор объекта, который принимает экземпляр класса параметров.

Ответ 3

Если целью является просто уменьшить количество аргументов, предоставленных конструктору, есть много способов добиться этого. Реальный вопрос заключается в том, что, насколько я понимаю из комментариев к моему первому сообщению, есть ли более простой способ управления параметрами.

Одним из способов упрощения управления параметрами является использование общей структуры данных для их поддержания. Что-то вроде карты.

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();
    }

};

И он будет использоваться следующим образом:

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

Атрибуты будут отрываться от класса AttrBase, который будет знать, как делегировать фактический атрибут.

template <AttrTag> struct Attr;

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

И атрибуты будут созданы из специализированного шаблона Attr, который наследуется от 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) {}
};

Это позволяет добавлять дополнительные атрибуты без увеличения сложности вашего конструктора. Кроме того, один и тот же набор атрибутов может быть передан различным объектам, и каждый из них может реагировать только на те атрибуты, которые им интересны. Необходимо установить только подмножество атрибутов. Можно проверить наличие атрибута или использовать значение по умолчанию. Если вы хотите добавить и удалить динамические атрибуты, контейнер может быть расширен для этого, добавив дополнительную карту для их хранения.