【问题标题】:How many constructors should a class have?一个类应该有多少个构造函数?
【发布时间】:2009-01-29 06:29:26
【问题描述】:

我目前正在修改一个具有 9 个不同构造函数的类。现在总的来说,我认为这个类的设计非常糟糕......所以我想知道一个类有这么多构造函数是否是糟糕的设计。

出现了一个问题,因为我最近向这个类添加了两个构造函数,试图重构和重新设计一个类(下面代码中的 SomeManager),以便它是可单元测试的,并且不依赖于它的每一个方法静止的。但是,由于其他构造函数被方便地隐藏在类开头下方大约一百行的位置,所以当我添加构造函数时,我没有发现它们。

现在发生的情况是调用这些其他构造函数的代码依赖于已经实例化的 SomeManager 类,因为它曾经是静态的......结果是空引用异常。

所以我的问题是如何解决这个问题?通过尝试减少构造函数的数量?通过让所有现有的构造函数接受一个 ISomeManager 参数?

当然,一个类不需要 9 个构造函数! ...哦,最重要的是,这个文件中有 6000 行代码!

这是我上面所说的构造函数的审查表示:

public MyManager()
    : this(new SomeManager()){} //this one I added

public MyManager(ISomeManager someManager) //this one I added
{
    this.someManager = someManager;
}

public MyManager(int id)
    : this(GetSomeClass(id)) {}

public MyManager(SomeClass someClass)
    : this(someClass, DateTime.Now){}

public MyManager(SomeClass someClass, DateTime someDate)
{
    if (someClass != null)
       myHelper = new MyHelper(someOtherClass, someDate, "some param");
}

public MyManager(SomeOtherClass someOtherClass)
    : this(someOtherClass, DateTime.Now){}

public MyManager(SomeOtherClass someOtherClass, DateTime someDate)
{
    myHelper = new MyHelper(someOtherClass, someDate, "some param");
}

public MyManager(YetAnotherClass yetAnotherClass)
    : this(yetAnotherClass, DateTime.Now){}

public MyManager(YetAnotherClass yetAnotherClass, DateTime someDate)
{
    myHelper = new MyHelper(yetAnotherClass, someDate, "some param");
}

更新:

感谢大家的回复...他们非常棒!

只是想我会更新一下我最终做了什么。

为了解决空引用异常问题,我修改了其他构造函数以采用 ISomeManager。

目前,在允许重构这个特定类时,我束手无策,所以我会将其标记为待办事项列表中的一个类,以便在我有空闲时间时重新设计。目前,我很高兴能够重构 SomeManager 类……它和 MyManager 类一样庞大而可怕。

当我开始重新设计 MyManager 时,我会寻找一种方法来将功能提取到两个或三个不同的类中......或者确保遵循 SRP 需要多少。

最终,我还没有得出任何给定类都有最大数量的构造函数的结论,但我相信在这个特定的实例中,我可以创建两个或三个类,每个类都有两个或三个构造函数..

【问题讨论】:

    标签: class-design constructor


    【解决方案1】:

    一个班级应该做一件事,而且只做一件事。如果它有这么多的构造函数,这似乎是一个迹象,表明它做了太多的事情。

    使用多个构造函数来强制在各种情况下正确创建对象的实例,但 9 似乎很多。我怀疑那里有一个接口,并且可以拖出接口的几个实现。每一个都可能有从一到几个与他们的专长相关的构造函数。

    【讨论】:

    • 构造函数(或重构)不应该是拥有接口的原因。当您必须在类中添加功能时需要接口。你可以说,哺乳动物(父母)猫的行走能力(IWalk)。
    【解决方案2】:

    尽可能少,
    尽可能多。

    【讨论】:

    • little:尽可能少,然后转到必要必要:尽可能多,然后转到很少(伪无限循环)
    【解决方案3】:

    9 个构造函数和 6000 行的类是代码异味的标志。您应该重新考虑该类。 如果班级有很多责任,那么您应该将它们分开。如果职责相似但几乎没有偏差,那么您应该寻求实现继承购买创建接口和不同的实现。

    【讨论】:

      【解决方案4】:

      如果你任意限制一个类中构造函数的数量,你最终可能会得到一个具有大量参数的构造函数。我会选择一个有 100 个构造函数的类,而不是一个每天有 100 个参数的构造函数。当你有很多构造函数时,你可以选择忽略其中的大部分,但是你不能忽略方法参数。

      将类中的构造函数集合想象成一个数学函数,将 M 个集合(其中每个集合是单个构造函数的参数列表)映射到给定类的 N 个实例。现在说,Bar 类可以在其构造函数之一中使用Foo,而Foo 类将Baz 作为构造函数参数,如下所示:

          Foo --> Bar
          Baz --> Foo
      

      我们有向 Bar 添加另一个构造函数的选项,这样:

          Foo --> Bar
          Baz --> Bar
          Baz --> Foo
      

      这对 Bar 类的用户来说很方便,但是由于我们已经有了从 Baz 到 Bar 的路径(通过 Foo),所以我们不需要那个额外的构造函数。因此,这就是判断调用所在的位置。

      但是,如果我们突然添加了一个名为Qux 的新类,并且我们发现自己需要从中创建Bar 的实例:我们必须在某处 添加一个构造函数。所以它可能是:

          Foo --> Bar
          Baz --> Bar
          Qux --> Bar
          Baz --> Foo
      

      或者:

          Foo --> Bar
          Baz --> Bar
          Baz --> Foo
          Qux --> Foo
      

      后者将在类之间更均匀地分配构造函数,但它是否是一个更好的解决方案很大程度上取决于它们的使用方式。

      【讨论】:

        【解决方案5】:

        答案:1(关于注射剂)。

        这是一篇关于该主题的精彩文章:Dependency Injection anti-pattern: multiple constructors

        总之,你的类的构造函数应该用于注入依赖,你的类应该对它的依赖开放。依赖项是您的班级需要的东西。不是它想要的东西,或者它想要的东西,但可以没有。这是它需要的东西。

        因此,拥有可选的构造函数参数或重载的构造函数对我来说毫无意义。您唯一的公共构造函数应该定义您的类的一组依赖项。这是你的班级提供的合同,上面写着“如果你给我一个IDigitalCamera、一个ISomethingWorthPhotographing和一个IBananaForScale,我会给你你能想象到的最好的IPhotographWithScale。但如果你吝啬任何这些事情,你都靠自己”。

        这是 Mark Seemann 撰写的一篇文章,探讨了拥有规范构造函数的一些更好的理由:State Your Dependency Intent

        【讨论】:

          【解决方案6】:

          您需要担心重构的不仅仅是这个类。这也是所有其他类。这可能只是您的代码库中的一个线程。 你有我的同情...我在同一条船上。 Boss 想要所有的东西都进行单元测试,不想重写代码,所以我们可以进行单元测试。最终做一些丑陋的黑客来使它工作。 您将不得不重写使用静态类的所有内容以不再使用它,并且可能会传递更多内容......或者您可以将其包装在访问单例的静态代理中。这样你至少可以模拟出单例,并以这种方式进行测试。

          【讨论】:

            【解决方案7】:

            您的问题不在于构造函数的数量。拥有 9 个构造函数比平常多,但我认为这不一定是错误的。这当然不是你问题的根源。真正的问题是最初的设计都是静态方法。这确实是类过于紧密耦合的一种特殊情况。现在失败的类与函数是静态的想法有关。在有问题的班级中,您对此无能为力。如果你想让这个类成为非静态类,你必须撤消其他人写入代码中的所有耦合。将类修改为非静态,然后更新所有调用者以首先实例化一个类(或从单例中获取一个)。找到所有调用者的一种方法是将函数设为私有并让编译器告诉您。

            在 6000 行时,该类不是很有凝聚力。它可能试图做太多事情。在一个完美的世界中,您可以将类(以及调用它的人)重构为几个更小的类。

            【讨论】:

              【解决方案8】:

              足以完成它的任务,但请记住单一职责原则,它指出一个类应该只具有单一职责。考虑到这一点,可能很少有情况下有 9 个构造函数是有意义的。

              【讨论】:

                【解决方案9】:

                我限制我的班级只有一个 real 构造函数。我将 real 构造函数定义为具有主体的构造函数。然后我有其他构造函数,它们只是根据它们的参数委托给真正的构造函数。基本上,我正在链接我的构造函数。

                查看您的类,有四个具有主体的构造函数:

                public MyManager(ISomeManager someManager) //this one I added
                {
                    this.someManager = someManager;
                }
                
                public MyManager(SomeClass someClass, DateTime someDate)
                {
                    if (someClass != null)
                       myHelper = new MyHelper(someOtherClass, someDate, "some param");
                }
                
                public MyManager(SomeOtherClass someOtherClass, DateTime someDate)
                {
                    myHelper = new MyHelper(someOtherClass, someDate, "some param");
                }
                
                public MyManager(YetAnotherClass yetAnotherClass, DateTime someDate)
                {
                    myHelper = new MyHelper(yetAnotherClass, someDate, "some param");
                }
                

                第一个是您添加的。第二个类似于最后两个,但有一个条件。最后两个构造函数非常相似,除了参数的类型。

                我会尝试找到一种方法来创建一个真正的构造函数,让第三个构造函数委托给第四个构造函数,或者反过来。我不确定第一个构造函数是否可以适应,因为它所做的事情与旧构造函数完全不同。

                如果您对这种方法感兴趣,请尝试查找 Refactoring to Patterns 一书的副本,然后转到 Chain Constructors 页面。

                【讨论】:

                  【解决方案10】:

                  当然,一个类应该有该类所需的尽可能多的构造函数...这并不意味着糟糕的设计可以接管。

                  类设计应该是构造函数在完成后创建一个有效的对象。如果你可以用 1 个参数或 10 个参数做到这一点,那就这样吧!

                  【讨论】:

                    【解决方案11】:

                    在我看来,这门课是用来做很多事情的。我认为您确实应该重构该类并将其拆分为几个更专业的类。然后你就可以摆脱所有这些构造函数,拥有更干净、更灵活、更易维护和更易读的代码。

                    这不是直接回答您的问题,但我确实相信,如果一个类有必要拥有超过 3-4 个构造函数,则表明它可能应该重构为多个类。

                    问候。

                    【讨论】:

                      【解决方案12】:

                      我可以从您的代码中看到的唯一“合法”情况是,如果其中一半使用的是您正在努力从代码中删除的过时类型。当我像这样工作时,我经常有两组构造函数,其中一半被标记为@Deprecated 或@Obsolete。但是你的代码似乎已经超出了那个阶段......

                      【讨论】:

                        【解决方案13】:

                        我一般都有一个,可能有一些默认参数。构造函数只会对对象进行最小设置,因此它在创建时有效。如果我需要更多,我将创建静态工厂方法。有点像这样:

                        class Example {
                        public:
                          static FromName(String newname) { 
                            Example* result = new Example();
                            result.name_ = newname;
                            return result;
                          }
                          static NewStarter() { return new Example(); }
                        
                        private:
                          Example();
                        }
                        

                        好吧,这实际上不是一个很好的例子,我会看看我是否能想到一个更好的并编辑它。

                        【讨论】:

                          【解决方案14】:

                          遮阳篷是:无

                          看看语言迪伦。它有另一个系统。

                          在构造函数中,您向槽(成员)添加更多值,然后是其他语言。您可以添加“初始化关键字”。然后,如果您创建一个实例,您可以将插槽设置​​为您想要的值。

                          当然,您可以设置 'required-init-keyword:' 并且您可以使用更多选项。

                          它很有效,而且很简单。我不想念旧系统。编写构造函数(和析构函数)。

                          (顺便说一句。它仍然是一种非常快的语言)


                          【讨论】:

                            【解决方案15】:

                            我认为具有多个构造函数的类具有多个职责。不过,如果能说服相反的观点,那就太好了。

                            【讨论】:

                              【解决方案16】:

                              构造函数应该只具有创建该类的实例所必需的那些参数。所有其他实例变量都应具有相应的 getter 和 setter 方法。如果您计划在将来添加新的实例变量,这将使您的代码更加灵活。

                              其实遵循OO原则——

                              1. 每个类的设计目标是低耦合和高内聚
                              2. 类应该对扩展开放,对修改关闭。

                                你应该有这样的设计 -

                                导入静态 org.apache.commons.lang3.Validate.*; 公共类员工 { 私有字符串名称; 私人雇员(){}

                                公共字符串 getName() { 返回名称; }

                                公共静态类 EmployeeBuilder { private final Employee 雇员;

                                public EmployeeBuilder()
                                {
                                    employee = new Employee();
                                }
                                
                                public EmployeeBuilder setName(String name)
                                {
                                    employee.name = name;
                                    return this;
                                }
                                
                                public Employee build()
                                {
                                    validateFields();
                                    return employee;
                                }
                                
                                private void validateFields()
                                {
                                    notNull(employee.name, "Employee Name cannot be Empty");
                                }
                                

                                } }

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 2020-01-09
                                • 1970-01-01
                                • 1970-01-01
                                • 2011-03-17
                                • 2017-03-29
                                • 2018-05-13
                                相关资源
                                最近更新 更多