【问题标题】:Should constructors accept parameters or should I create setters?构造函数应该接受参数还是应该创建设置器?
【发布时间】:2012-10-01 02:53:55
【问题描述】:

我有两个选择。要么创建一个在其构造函数中接受许多参数的类,要么创建许多 setter 方法和一个 init 方法。我不确定哪个是首选选项,是否应该在构造函数中接受某些参数,而其他参数可以通过 setter 手动设置?还是我想多了?

这是一个相关的问题,也是我提出的:Conflicts between member names and constructor argument names

【问题讨论】:

  • 您对某些值有合理的默认值吗?
  • " 要么创建一个在其构造函数中接受许多参数的类,要么创建许多 setter 方法和一个 init 方法。" -- 错误的困境。通常,第一个选项更好。但是如果可能的话,重新设计第三个选项,使您的构造函数具有更少的参数并且不需要设置器或初始化函数。
  • @Bane:越来越多的我发现自己根本没有提供 setter,至少对于只有几个字段的对象。如果您想要给定字段的不同值,则只需使用这些值创建一个新对象。虽然在 3 或 4 个字段之后它并不是真正易于管理......但这只会让我的对象更小:D
  • 你应该重新设计你的班级,一个班级/一份工作的负责人通常会帮助你做到这一点。当然,您也可以将这些参数封装在包装器中。

标签: c++ oop class


【解决方案1】:

如果在创建对象后,您必须调用 setinit 才能实际使用它......好吧,这只是一个糟糕的设计。

如果对象在没有按照您希望的方式初始化某些成员的情况下可用,则可以稍后设置它们。

这里的黄金法则是 - 如果您创建了一个对象,您应该能够在不进行任何其他类型的初始化的情况下使用它

扩展答案:

假设您有一个形状,它有 10 个边、10 个角、一种颜色和一个名称,可以连接到不同的形状。构造函数应如下所示:

 MyShape(Point c1, Point c2,...., Point c10, Color c, Name n)

如您所见,我省略了连接形状,因为如果当前对象未连接,它可以设置为NULL。但是,在没有任何其他参数的情况下,对象是无效的,因此应该在构造函数中设置它们。

可能的重载(或者默认参数)可以是:

 MyShape(Point c1, Point c2,...., Point c10, Color c, Name n, 
                                      MyShape* connectedShape /*=NULL*/)

【讨论】:

  • 好吧,对象可以运行,但大多数参数将是默认值 (0)。
  • @Bane 如果这些默认值有意义,那为什么不呢。另一方面,如果这些默认值在大多数情况下被随后的set 调用覆盖,那可能就是垃圾。更重要的是,如果值不会在初始化时改变,那么无论如何都不需要设置器。在这种情况下,具有许多参数的构造函数绝对是最有意义的,可能借助默认参数来简化其使用。
  • @Bane:请记住,如果您有一个可以从几组不同的参数集设置的对象,那么您也可以有多个构造函数。
  • 开始不完整的对象会在以后导致很多错误,特别是如果仅有时使用缺失值。这是基本的 OO 原则,同样适用于 Java 和 C#。
  • @ChristianRau:我同意,但我要补充一点,在某些情况下,多个显式构造函数可能比默认参数更好。例如,如果客户端没有指定argB,那么指定argA 是没有意义的,那么int argA = 0, int argB = 0 允许使用一组令人困惑/误导的参数。
【解决方案2】:

这取决于你在做什么。通常最好在构造函数中设置一些东西,因为这些有助于塑造对象在其生命周期后期的使用方式。一旦创建了对象(例如计算因子或文件名),更改值也可能会产生影响,这可能意味着您必须提供重置对象的功能 - 非常混乱。

有时会提供一个在构造函数之后调用的初始化函数的参数(当调用纯虚函数时,很难直接从构造函数初始化),但是你必须保留对象状态的记录,这会增加更多的复杂性.

如果对象是一个直接的无状态数据容器,那么访问器和修改器可能没问题,但它们会增加大量维护开销,而且很少会全部使用。

我倾向于坚持在构造函数中设置你的值,然后添加访问器以及何时允许你只读访问你可能需要的参数。

【讨论】:

    【解决方案3】:

    这取决于您的架构和工具:

    如果您计划开发/原型化大型 OO 层次结构,如果您没有好的 IDE/编辑器,我将不愿意通过构造函数传递大量信息。在这种情况下,您可能会在每个重构步骤中完成大量工作,这可能会导致错误,而编译器无法捕捉到。

    如果您计划使用一组良好集成的对象集(例如,通过大量使用设计模式),这些对象不跨越一个大的层次结构,而是具有强大的迭代,通过构造函数传递更多数据是一件好事,因为更改一个对象构造函数不会破坏所有子构造函数。

    【讨论】:

    • 我不明白你反对构造函数的论点。你的意思是 IDE 不能很好地支持它?
    • 我指的是从基类派生的构造函数。更改基类(例如决定不传递对象)会迫使您更改所有派生构造函数。当然,这只发生在更大的层次结构中。
    • 其实我想说的是这里有利于构造函数。因为很容易忘记在某处添加对 setter 的调用(因此必须触发运行时错误),而编译器会检查您没有忘记修改任何派生构造函数(和构造站点)自动!
    • @Matthieu:没错,编译器在常见情况下会捕捉到这一点。在原型设计/开发层次结构的情况下,我指的不是设计,而是工作流程。如果一个人决定更具表现力和 f.e.将一个参数更改为 const,它可以很容易地刷新很多时间来修复层次结构,而不是固定一个 setter。也就是说,我完全同意 Lucians 和 Zdeslav 的回答。
    【解决方案4】:

    您应该为保留class invariant 所必需的所有成员提供构造函数参数。换句话说,对象从创建到销毁的那一刻都应该处于有效且一致的状态。其他一切都在招惹麻烦。

    话虽如此,有时也会做出让步,例如在需要调用虚拟方法以提供特定于类型的初始化的层次结构的情况下。通常,这可以通过使用模板类/方法来避免(即static polymorphism

    如果有不影响类不变量的类成员,可以稍后通过 setter 或其他方法设置它们。

    【讨论】:

    • 如果需要通过虚拟调用进行特定设置,您可能需要挖掘 Builder/Factory 模式。
    • 你是对的,有一些变通方法,但我发现它们都不优雅 :) Builder 可能最接近那个
    【解决方案5】:

    the builder pattern 将在此处帮助您尝试合并参数以使它们在构建器的设置过程中有意义

    【讨论】:

    • Builder 模式是一个不错的选择——就像拥有命名参数和不可变类一样。
    • 唯一的问题是典型的构建器模式不能保证在编译时所有的参数都已经包含了
    • @Catskul 然后,您可以创建合理的默认值或抛出异常,或者在构建器的同一方法中提供属于一起的参数
    • 我想出了一个具有编译时检查的解决方案,但根据情况可能过于复杂:stackoverflow.com/a/17820333/106797
    【解决方案6】:

    如果设置是必需的并且不能被赋予默认值,则在构造函数中使其成为必需。这样你就知道它实际上会被设置。

    如果设置不是必需的并且可以给定一个默认值,请为其创建一个setter。这让构造函数变得简单了很多。

    例如如果您有一个发送电子邮件的类,则构造函数中可能需要“To”字段,但其他所有内容都可以在 setter 方法中设置。

    【讨论】:

      【解决方案7】:

      我的经验告诉我在构造函数中使用参数而不是 getter 和 setter。如果你有很多参数,它建议可选的可以默认,而必填/强制的是构造函数参数。

      【讨论】:

        【解决方案8】:

        根据经验,有很多构造函数参数是一个类做太多事情的标志,所以先试着把它分成更小的类。

        然后尝试将一些参数分组到更小的类或结构中,每个类或结构都有自己的、更简单的构造函数。

        如果你有合理的默认值,你可以使用一个构造函数,它只为构造一个新对象时绝对必须给出的值提供参数,然后添加 setter,或者使用复制“starter”对象的静态函数,改变过程中的一部分。这样,您始终拥有一致的对象(不变量 OK),以及更短的构造函数或函数调用。

        【讨论】:

          【解决方案9】:

          我同意 ratchet freaksuggestion 构建器模式,但需要权衡的是典型的构建器模式不提供编译时检查以确保已包含所有参数,并且您可以结束使用不完整/错误构建的对象。

          这对我来说已经够大的问题了,我制作了一个编译时检查版本,如果你能原谅额外的机器,它可能会为你完成这项工作。 (当然也有优化)

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

          【讨论】:

            猜你喜欢
            • 2012-01-21
            • 2021-02-03
            • 1970-01-01
            • 2016-09-29
            • 2014-07-25
            • 1970-01-01
            • 2016-02-20
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多