一个班级应该有多少个构造函数?

时间:2009-01-29 06:29:26

标签: class-design constructor

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

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

16 个答案:

答案 0 :(得分:21)

一个班级应该做一件事,一件事。如果它有这么多的构造函数,它似乎是一个告诉故事标志,它做了太多的事情。

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

答案 1 :(得分:15)

尽可能少,
尽可能多。

答案 2 :(得分:7)

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

答案 3 :(得分:3)

如果你任意限制一个类中的构造函数的数量,你最终可能会得到一个具有大量参数的构造函数。我会在每天有100个参数的构造函数上使用100个构造函数的类。如果你有很多构造函数,你可以选择忽略它们中的大多数,但你不能忽略方法参数。

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

    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

OR:

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

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

答案 4 :(得分:3)

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

以下是关于该主题的精彩文章:Dependency Injection anti-pattern: multiple constructors

总结一下,你的类的构造函数应该用于注入依赖项,你的类应该公开它的依赖项。依赖是您的课程所需要的。不是它想要的东西,或者它想要的东西,但是可以没有。这是它需要的东西。

因此,拥有可选的构造函数参数或重载的构造函数对我来说毫无意义。您唯一的公共构造函数应定义您的类的依赖项集。这是你的班级提供的合同,上面写着“如果你给我IDigitalCameraISomethingWorthPhotographingIBananaForScale,我会给你最好的IPhotographWithScale你可以想象。但如果你吝啬任何这些事情,那就是你自己“。

以下是Mark Seemann撰写的一篇文章,其中介绍了使用规范构造函数的一些更好的理由:State Your Dependency Intent

答案 5 :(得分:2)

这不仅仅是这门课你还要担心重新分解。这也是所有其他课程。这可能只是纠结的绞纱中的一个线程,也就是你的代码库。 你有我的同情......我在同一条船上。 老板想要所有单元测试,不想重写代码,所以我们可以进行单元测试。最后做一些丑陋的黑客让它工作。 您将不得不重新编写使用静态类的所有内容,以便不再使用它,并且可能将其传递给更多...或者您可以将其包装在访问单例的静态代理中。这样你至少可以嘲笑单身人士,并以这种方式进行测试。

答案 6 :(得分:2)

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

在6000行,课程不是很有凝聚力。它可能试图做太多。在一个完美的世界中,你会将类(以及那些调用它)重构为几个较小的类。

答案 7 :(得分:1)

足够完成任务,但要记住单一责任原则,该原则规定一个班级应该只承担一项责任。考虑到这一点,可能很少有9个构造函数有意义的情况。

答案 8 :(得分:1)

我将我的课程限制为只有一个真正的构造函数。我将 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");
}

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

我会尝试找到一种方法来创建一个真正的构造函数,使第三个构造函数委托给第四个或另一个方法。我不确定第一个构造函数是否可以适应,因为它正在做一些与旧构造函数完全不同的东西。

如果您对此方法感兴趣,请尝试查找“重构为模式”一书的副本,然后转到Chain Constructors页面。

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

在我看来,这门课程已经习惯了。我认为你真的应该重构这个类并将它分成几个更专业的类。然后,您可以摆脱所有这些构造函数,并拥有更清晰,更灵活,更易于维护和更易读的代码。

这不是你问题的直接答案,但我确实认为,如果一个类有超过3-4个构造函数的必要性,则表明它可能应该重构为几个类。

的问候。

答案 11 :(得分:0)

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

答案 12 :(得分:0)

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

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

private:
  Example();
}

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

答案 13 :(得分:0)

awnser是:NONE

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

构造函数的实例,您可以使用其他语言向插槽(成员)添加更多值。您可以添加“init-keyword”。然后,如果您创建实例,则可以将插槽设置​​为所需的值。

当然你可以设置'required-init-keyword:',​​你可以使用更多的选项。

它很有效,很容易。我不会错过旧系统。编写构造函数(和析构函数)。

(顺便说一下,它仍然是一种非常快速的语言)


答案 14 :(得分:0)

我认为具有多个构造函数的类具有多个责任。不过很高兴相信相反的事情。

答案 15 :(得分:0)

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

事实上遵循OO原则 -

  1. 对于低耦合和高内聚的每个类设计目标
  2. 应该打开类进行扩展,但关闭以进行修改。

    你的设计应该像 -

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

    } }