4

我想要一个关于如何避免大量构造函数参数的通用解决方案。我在此处提供的示例只是示例,对于此处的确切示例,我不想要具体的答案。话虽如此,我的问题显然是在我的构造函数中有太多参数。

我有一个用于任何类型的人(士兵、巫师、商人等)的基类,称为Person. 我的Person课程相当简单,它实现了每个人都有的基本事物。假设我有以下属性,我的构造函数为每个属性接受一个参数:

  • string firstName
  • string surname
  • uint health
  • uint maxHealth- 我们不希望任何人拥有 999999999 的健康...
  • uint movementSpeed- 并不是每个人都以同样的速度奔跑,对吧?

所以构造函数看起来像:

Person::Person(const string &firstName="Missing first name",
               const string &surname="Missing surname",
               const uint health=100,
               const uint maxHealth=100,
               const uint movementSpeed=50) :
    m_firstName(firstName),
    m_surname(surname),
    m_health(health),
    m_maxHealth(maxHealth),
    m_movementSpeed(movementSpeed)
{}

现在想象一个新类,在继承树的深处,称为Wizard. 大多数巫师都是为了战斗而存在的,所以他们也可以攻击等等。巫师的构造函数可能如下所示:

Wizard::Wizard(const string &firstName="Missing first name",
               const string &surname="Missing surname",
               const string &wizardName="Missing wizard name",
               const uint health=100,
               const uint maxHealth=100,
               const uint mana=100,
               const uint maxMana=100,
               const uint strength=50, // physical damage
               const uint witchcraft=50, // spell damage
               const uint armor=30, // vs. physical damage
               const uint resistance=30, // vs. spell damage
               const uint movementSpeed=50) :
    Warrior(firstName, surName, health, maxHealth, strength, armor, resistance, movementSpeed),
    m_wizardName(wizardName),
    m_mana(mana),
    m_maxMana(maxMana),
    m_witchcraft(witchcraft)
{}

可能还有更多的争论,但我想你明白了。这看起来可能还不错,但想象一下看一个陌生人的代码并看到如下内容:

Wizard *wiz = new Wizard("Tom", "Valedro", "Lord Voldemort", 300, 400, 200, 200, 10, 500, 30, 400, 300)

有人可能会说“还不错,看文档就行了!” 海事组织。这太糟糕了,并且使代码非常难以阅读甚至编写。此外,参数的顺序也很难让人跟踪。

我想到的解决方案很少,但它们也有缺点。一件事是使用默认构造函数并且根本不给它任何参数,然后使用 setter 来完成这项工作:

Wizard *wiz = new Wizard;
wiz->setFirstName("Tom");
wiz->setSurname("Valedro");
...

当然,这会产生几十行额外的文本,而且有些人几乎不反对 getter 和 setter。它将摆脱没有意义的数字,至少你可以阅读每个数字的作用(wiz->setHealth(100);显然告诉我们在这里设置健康)。

另一种解决方案是将一些属性组合到结构中。我可以轻松地将firstName,surnamewizardName( 甚至更好地使用nickname) 合并到一个Name类或结构中。这将减少我在此示例中的论点数量,但正如我所说,我想要一个针对此类问题的一般答案。即使在组合了其中一些参数之后,仍然可能存在非常多的论点,或者您可能无法组合任何原因它们根本不相似。

4

6 回答 6

2

您可以使用“流利的构造函数”

例如,请参阅此链接:http ://richarddingwall.name/2009/06/01/fluent-builder-pattern-for-classes-with-long-ish-constructors/

于 2013-05-15T15:05:22.690 回答
2

将这些参数分组到收集合理相关信息的数据结构中,并让您的构造函数接受这些数据结构。

将所有内容分组到一个数据结构中的简单解决方案将使您的构造函数只接受一个参数,但只会将问题转移到数据结构的构造函数(前提是您要定义一个)。

因此,您需要找到正确的平衡点,以便您的构造函数将接受合理数量的参数(数据结构)——我会说绝对不超过 5 个——并且每个数据结构将“属于彼此”的信息组合在一起.

现在,既然你要求一个抽象的答案,如果我想保持绝对一般性,这就是我所能做到的。关于一个具体的例子,你可以有:

struct name_info
{
    // Constructor(s)...

    const std::string firstName;
    const std::string surname;
    const std::string wizardName;
};

struct health_info
{
    // Constructor(s)...

    const uint health;
    const uint maxHealth;
    const uint mana;
    const uint maxMana;
};

struct fight_info
{
    // You got it...
};

然后你的Wizard构造函数看起来像:

Wizard::Wizard(name info const& ni, health_info const& hi, fight_info const& fi)
于 2013-05-15T15:06:48.030 回答
2

这是单元测试中的常见问题。一个好的测试是可读的,正如您所指出的,一串神奇的数字绝不是。一种推荐的做法是引入“解释变量”。使用参数名称作为测试类中的局部变量。

string firstName("Tom");
string surname("Valedro");
string wizardName("Lord Voldemort");
uint health=300;
uint maxHealth=400;
uint mana=200;
uint maxMana=200;
uint strength=10; // physical damage
uint witchcraft=500; // spell damage
uint armor=30; // vs. physical damage
uint resistance=400; // vs. spell damage
uint movementSpeed=300;

Wizard *wiz = new Wizard( firstName, surname, wizardName, health, maxHealth, 
                          mana, maxMana, strength, witchcraft, 
                          armor, resistance, movementSpeed );

现在,当我查看那个构造函数调用时,我确切地知道它在测试什么,因为它在我面前拼写出来了。

在过早的优化器过早抱怨之前,这种做法不会增加生产程序的大小或速度。那里的每个优化编译器都会将此调用优化成一堆文字。这种编码风格所做的就是通过使代码更清晰、更易读来影响可维护性。

于 2013-05-15T18:49:15.757 回答
1

除了其他人的建议之外,为了提高参数的清晰度,您可能需要尝试以下类似的方法。包装器的显式构造函数将阻止某人将 int 隐式传递给 Stats 构造函数,因此...

Stats a(10, 18, 19); // will not compile
Stats b(Health(10), Health(18), Mana(19)); // will compile

.

// int wrappers
struct Health {
  explicit Health(int v)
  : _val(v) 
  {}

  int _val;
};

struct Mana {
  explicit Mana(int v)
  : _val(v) 
  {}

  int _val;
};

struct MoveSpeed{
  explicit MoveSpeed(int v)
  : _val(v) 
  {}

  int _val;
};


struct Stats{
  Stats(const Health& maxhealth, const Health& curhealth, const Mana& maxmana/*... etc*/)
  : _maxhealth(maxhealth)
  , _curhealth(curhealth)
  , _maxmana(maxmana)
  // ... etc
  {}

  Health _maxhealth;
  Health _curhealth;
  Mana _maxmana ;
  // ... etc
};


class Warrior{
  public:
    Warrior(const Stats& stats /* ... etc */)
    : _stats(stats)
    // ... etc
    {}

  private:
    Stats _stats;
};
于 2013-05-15T15:48:28.880 回答
0

我想你会发现,如果你对你的论点进行分类并根据这些类别将它们分组到结构中,你将不会有很多论点。如果需要,您也可以分层执行此操作。

struct Stats
{
     uint health;
     uint maxHealth;
     uint mana;
     uint maxMana;
     uint strength;
     uint witchcraft;
     uint armor;
     uint resistance;
     uint movementSpeed;
};

class Wizard
{
public:
    static Stats defaultWizardStats;

    Wizard(Name name, Stats stats = defaultWizardStats)
    : m_Name(name)
    , m_Stats(stats)
    {}

private:
    Name m_Name;
    Stats m_Stats;
};

您也可以使用这些组将信息存储在您的班级中。

于 2013-05-15T15:11:27.753 回答
0

我一直喜欢解决这个问题的构建器模式,但几乎从不使用它,因为它不能确保在编译时所有参数都已包含在内。

这个解决方案有点混乱,但可以完成工作。这将是一个不错的选择,尤其是在完成代码生成的情况下。

#include <boost/shared_ptr.hpp>

class Thing
{
    public:

        Thing( int arg0, int arg1 )
        {
            std::cout << "Building Thing with   \n";
            std::cout << "    arg0: " << arg0 << "\n";
            std::cout << "    arg1: " << arg1 << "\n";
        }

        template <typename CompleteArgsT>
        static
        Thing BuildThing( CompleteArgsT completeArgs )
        {
            return Thing( completeArgs.getArg0(), 
                          completeArgs.getArg1() );
        }


    public:

        class TheArgs
        {
            public:
                int arg0;
                int arg1;
        };

        class EmptyArgs
        {   
            public:    
                EmptyArgs() : theArgs( new TheArgs ) {};
                boost::shared_ptr<TheArgs> theArgs;    
        };

        template <typename PartialArgsClassT>
        class ArgsData : public PartialArgsClassT
        {
            public:
                typedef ArgsData<PartialArgsClassT> OwnType;

                ArgsData() {}
                ArgsData( const PartialArgsClassT & parent ) : PartialArgsClassT( parent ) {}

                class HasArg0 : public OwnType
                {
                    public:
                        HasArg0( const OwnType & parent ) : OwnType( parent ) {}
                        int getArg0() { return EmptyArgs::theArgs->arg0; }
                };

                class HasArg1 : public OwnType
                {
                    public:
                        HasArg1( const OwnType & parent ) : OwnType( parent ) {}                    
                        int getArg1() { return EmptyArgs::theArgs->arg1; }
                };

                ArgsData<HasArg0>  arg0( int arg0 ) 
                { 
                    ArgsData<HasArg0> data( *this ); 
                    data.theArgs->arg0 = arg0;
                    return data; 
                }

                ArgsData<HasArg1>  arg1( int arg1 )
                { 
                    ArgsData<HasArg1> data( *this ); 
                    data.theArgs->arg1 = arg1;                    
                    return data; 
                }
        };

        typedef ArgsData<EmptyArgs> Args;
};



int main()
{
    Thing thing = Thing::BuildThing( Thing::Args().arg0( 2 ).arg1( 5 ) );
    return 0;
}
于 2013-07-23T20:15:13.487 回答