【问题标题】:Too many constructor arguments in deepest class of inheritance?最深的继承类中的构造函数参数太多?
【发布时间】:2013-05-10 05:28:47
【问题描述】:

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

我有一个用于任何类型的人(士兵、巫师、商人等)的基类,称为 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); 显然告诉我们在这里设置健康)。

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

【问题讨论】:

    标签: c++ design-patterns parameters constructor arguments


    【解决方案1】:

    【讨论】:

      【解决方案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)
      

      【讨论】:

        【解决方案3】:

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

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

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

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

        【讨论】:

        • 如此简单,却又如此巧妙!总有一天我会完全使用它! :) 尽管如此,构建器机制似乎更好,因为它消除了参数顺序的重要性。
        • 当然,如果语言支持,请使用它,因为它很干净。目标只是使代码可读。如果您遵循填充结构的建议,则相同的基本想法将起作用,因为每个元素都有一个可读的 struct.name=value; 构造,并且填充结构与顺序无关。
        【解决方案4】:

        除了其他人的建议之外,为了提高参数的清晰度,您可能需要尝试以下类似的方法。包装器的显式构造函数将阻止某人将 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;
        };
        

        【讨论】:

        • 为什么要为成员变量使用_ 前缀?一些带下划线前缀的变量是保留的(例如 _L 是由 Visual Studio 保留的)并且 _ 使它更难阅读。请切换到m_this->,或者根本不使用前缀。
        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

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

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

          #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;
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-14
            • 2015-12-02
            • 2019-12-14
            • 1970-01-01
            • 2011-12-08
            相关资源
            最近更新 更多