oop - 缺陷 : Constructor does Real Work

标签 oop unit-testing design-patterns constructor

我有一个代表一组数字的类。构造函数接受三个参数: startValueendValuestepSize
该类负责保存一个列表,其中包含考虑 stepSize 的开始值和结束值之间的所有值。

示例:startValue: 3, endValue: 1, stepSize = -1, Collection = { 3,2,1 }

我目前正在创建集合和一些关于构造函数中对象的信息字符串。公共(public)成员是只读信息字符串和集合。

我的构造函数目前做了三件事:

  • 检查参数;这可能会从构造函数
  • 中抛出异常
  • 将值填充到集合中
  • 生成信息字符串

  • 我可以看到我的构造函数确实在工作,但是我该如何解决这个问题,或者,我应该解决这个问题吗?如果我将“方法”从构造函数中移出,就像拥有 init 函数并给我留下一个未完全初始化的对象。我的对象的存在值得怀疑吗?或者在构造函数中完成一些工作不是那么糟糕,因为仍然可以测试构造函数,因为没有创建对象引用。

    对我来说,它看起来不对,但似乎我找不到解决方案。我也考虑了 builder ,但我不确定这是否正确,因为您无法在不同类型的创作之间进行选择。然而,单个单元测试的责任较小。

    我正在用 C# 编写代码,但我更喜欢通用解决方案,这就是为什么文本不包含代码的原因。

    编辑:感谢您编辑我糟糕的文字(:我改回了标题,因为它代表了我的观点,而编辑后的标题没有。我不是在问真正的工作是否有缺陷。对我来说,它是。看看这个引用。

    http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

    该博客很好地说明了问题。我仍然找不到解决方案。

    最佳答案

    促使你让构造函数保持轻量化的概念:

  • 控制反转(依赖注入(inject))
  • 单一职责原则(应用于构造函数而不是类)
  • 延迟初始化
  • 测试
  • K.I.S.S.
  • D.R.Y.

  • 链接到为什么的论点:
  • How much work should be done in a constructor?
  • What (not) to do in a constructor
  • Should a C++ constructor do real work?
  • http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

  • 如果您检查构造函数中的参数,如果这些参数来自任何其他来源(setter、构造函数、参数对象),则无法共享验证代码

    如果将值填充到集合中或在构造函数中生成无法与其他构造函数共享代码的信息字符串,则可能需要稍后添加。

    除了无法共享之外,还会延迟到真正需要(lazy init)。还有通过继承进行覆盖,它提供了更多选项,许多方法只做一件事,而不是一个做所有事情的构造函数。

    您的构造函数只需要将您的类置于可用状态。它不必完全初始化。但是使用其他方法来做真正的工作是完全免费的。那只是没有利用“懒惰的初始化”想法。有时你需要它,有时你不需要。

    请记住,构造函数所做或调用的任何内容都被用户/测试人员压在了喉咙里。

    编辑:

    你仍然没有接受答案,我睡了一会儿,所以我会尝试一下设计。一个好的设计是灵活的,所以我假设我不确定信息字符串是什么,或者我们的对象是否需要通过集合来表示一组数字(因此提供迭代器,大小( )、add()、remove() 等)或仅由一个集合支持并提供对这些数字的一些狭窄的专门访问(例如不可变)。

    这个小家伙就是参数对象模式
    /** Throws exception if sign of endValue - startValue != stepSize */
    ListDefinition(T startValue, T endValue, T stepSize);
    

    T 可以是 int 或 long 或 short 或 char。玩得开心,但要保持一致。
    /** An interface, independent from any one collection implementation */
    ListFactory(ListDefinition ld){
        /** Make as many as you like */
        List<T> build();
    }
    

    如果我们不需要限制对集合的访问,我们就完成了。如果我们这样做,在暴露它之前将它包裹在一个外观中。
    /** Provides read access only.  Immutable if List l kept private. */
    ImmutableFacade(List l);
    

    哦等等,需求改变了,忘记了“信息字符串”。 :)
    /** Build list of info strings */
    InformationStrings(String infoFilePath) {
         List<String> read();
    }
    

    不知道这是否是您的想法,但是如果您想要将行号计算为两位数的能力,那么您现在拥有它。 :)
    /** Assuming information strings have a 1 to 1 relationship with our numbers */
    MapFactory(List l, List infoStrings){
        /** Make as many as you like */
        Map<T, String> build();
    }
    

    所以,是的,我会使用构建器模式将所有这些连接在一起。或者您可以尝试使用一个对象来完成所有这些工作。由你决定。但我认为你会发现这些构造函数中很少有人做很多事情。

    编辑2
    我知道这个答案已经被接受,但我意识到还有改进的空间,我无法抗拒。上面的 ListDefinition 通过使用 getter 公开它的内容来工作,ick。有一个“告诉,不要问”的设计原则在这里被无缘无故地违反了。
    ListDefinition(T startValue, T endValue, T stepSize) {
        List<T> buildList(List<T> l);
    }
    

    这让我们构建任何类型的列表实现并根据定义对其进行初始化。现在我们不需要 ListFactory。 buildList 是我称之为分流的东西。它在对它做了一些事情后返回它接受的相同引用。它只是允许您跳过为新的 ArrayList 命名。现在制作一个列表看起来像这样:
    ListDefinition<int> ld = new ListDefinition<int>(3, 1, -1);
    List<int> l = new ImmutableFacade<int>(  ld.buildList( new ArrayList<int>() )  );
    

    哪个工作正常。有点难读。那么为什么不添加一个静态工厂方法:
    List<int> l = ImmutableRangeOfNumbers.over(3, 1, -1);
    

    这不接受依赖注入(inject),但它建立在接受的类上。它实际上是一个依赖注入(inject)容器。这使它成为底层类的流行组合和配置的一个很好的速记。您不必为每种组合都制作一个。对许多类执行此操作的重点是现在您可以将所需的任何组合放在一起。

    嗯,这是我的 2 美分。我要找点别的东西来沉迷。欢迎反馈。

    关于oop - 缺陷 : Constructor does Real Work,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23623516/

    相关文章:

    java - 使用备忘录模式(和命令)存储复杂对象的状态

    C++ 传递动态方法指针作为不同类的方法参数

    c# - 我可以访问内部类中的外部类对象吗

    .net - 为 .Net 选择模拟框架时应该考虑什么

    unit-testing - 没有使用scalatra的Jetty的servlet错误的multipartconfig

    java - 为什么即使实现了 Iterable,我也会收到 foreach 编译器错误?

    c# - 构建灵活且可重用的类层次结构

    oop - 理解 Dart 中的 'implements' 和 'with'

    c# - 为什么在声明中隐式向上转换?

    java - 如何对与系统(或 Android)类交互的方法进行单元测试