11

我目前正在修改一个具有 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 需要多少。

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

4

16 回答 16

21

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

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

于 2009-01-29T06:35:27.263 回答
16

尽可能少,尽可能
多。

于 2009-01-29T07:20:20.230 回答
7

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

于 2009-01-29T06:48:08.250 回答
3

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

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

    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

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

于 2009-01-29T07:16:32.047 回答
3

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

这是一篇关于该主题的精彩文章:依赖注入反模式:多个构造函数

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

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

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

于 2014-02-06T19:32:05.357 回答
2

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

于 2009-01-29T06:37:41.377 回答
2

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

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

于 2009-01-29T06:51:31.567 回答
1

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

于 2009-01-29T06:41:41.027 回答
1

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

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

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页面。

于 2009-01-29T08:26:31.017 回答
0

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

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

于 2009-01-29T06:40:43.243 回答
0

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

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

问候。

于 2009-01-29T06:48:56.953 回答
0

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

于 2009-01-29T06:50:18.637 回答
0

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

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

private:
  Example();
}

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

于 2009-01-29T06:57:04.800 回答
0

遮阳篷是:无

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

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

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

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

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


于 2010-03-25T14:18:22.157 回答
0

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

于 2012-12-19T15:11:13.940 回答
0

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

实际上遵循OO原则-

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

    你应该有这样的设计 -

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

    public String getName() { 返回名称;}

    公共静态类 EmployeeBuilder { 私有最终员工雇员;

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

    } }

于 2015-02-22T16:33:17.507 回答