【问题标题】:Is there any disadvantage of writing a long constructor?编写长构造函数有什么缺点吗?
【发布时间】:2011-02-27 03:53:13
【问题描述】:

它会影响加载应用程序的时间吗? 还是这样做的任何其他问题?

【问题讨论】:

  • “长”构造函数是什么意思?名义上?在参数列表中?无论算法复杂度如何,代码长度/行数?
  • @sharptooth - 我总是给变量起很长的名字,这样屏幕上的像素就会减少。
  • @CurtainDog:我用另一种方式解决了这个问题:我将编辑器设置更改为“绿色字母,黑色背景”——现在光照区域大大减小了,我不需要强制执行更长的变量为了能效而命名。

标签: oop


【解决方案1】:

“长”是什么意思的问题含糊不清。以下是一些可能的解释:

解读#1:构造函数有很多参数

具有许多参数的构造函数可能会导致可读性差,但存在更好的替代方案。

这里引用Effective Java 2nd Edition,Item 2:在面对许多构造函数参数时考虑构建器模式

传统上,程序员使用伸缩构造函数模式,在该模式中,您提供一个仅具有必需参数的构造函数,另一个具有单个可选参数,第三个具有两个可选参数,依此类推。 ..

伸缩构造函数模式本质上是这样的:

public class Telescope {
    final String name;
    final int levels;
    final boolean isAdjustable;

    public Telescope(String name) {
        this(name, 5);
    }
    public Telescope(String name, int levels) {
        this(name, levels, false);
    }
    public Telescope(String name, int levels, boolean isAdjustable) {       
        this.name = name;
        this.levels = levels;
        this.isAdjustable = isAdjustable;
    }
}

现在您可以执行以下任何操作:

new Telescope("X/1999");
new Telescope("X/1999", 13);
new Telescope("X/1999", 13, true);

但是,您目前不能只设置nameisAdjustable,而将levels 保留为默认值。你可以提供更多的构造函数重载,但显然这个数量会随着参数数量的增加而爆炸式增长,你甚至可能有多个 booleanint 参数,这真的会让事情变得一团糟。

如您所见,这不是一个令人愉快的编写模式,使用起来更不愉快(这里的“真”是什么意思?13 是多少?)。

Bloch 建议使用构建器模式,这样您就可以编写如下代码:

Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();

请注意,现在参数已命名,您可以按照您想要的任何顺序设置它们,并且您可以跳过您想要保留默认值的参数。这肯定比伸缩构造函数好得多,尤其是当有大量参数属于许多相同类型时。

另见

相关问题


解释#2:构造函数做了很多耗时的工作

如果工作必须在构造时完成,那么在构造函数或辅助方法中执行它并没有太大的区别。但是,当构造函数将工作委托给辅助方法时,请确保它不可被覆盖,因为这可能会导致很多问题。

这里引用了Effective Java 2nd Edition,Item 17: Design and document for inheritance,或者禁止它

为了允许继承,类必须遵守更多的限制。 构造函数不得直接或间接调用可覆盖的方法。如果您违反此规则,将导致程序失败。超类构造函数在子类构造函数之前运行,因此子类中的覆盖方法将在子类构造函数运行之前被调用。如果覆盖方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行。

这里有一个例子来说明:

public class ConstructorCallsOverride {
    public static void main(String[] args) {
        abstract class Base {
            Base() { overrideMe(); }
            abstract void overrideMe(); 
        }
        class Child extends Base {
            final int x;
            Child(int x) { this.x = x; }
            @Override void overrideMe() {
                System.out.println(x);
            }
        }
        new Child(42); // prints "0"
    }
}

这里,当Base构造函数调用overrideMe时,Child还没有完成对final int x的初始化,方法得到了错误的值。这几乎肯定会导致错误和错误。


解释#3:构造函数做了很多可以推迟的工作

当某些工作被推迟到真正需要的时候,可以加快对象的构建;这称为延迟初始化。例如,当构造 String 时,它实际上并不计算其哈希码。它只在第一次需要哈希码时才这样做,然后它会缓存它(因为字符串是不可变的,所以这个值不会改变)。

但是,请考虑 Effective Java 2nd Edition,Item 71:明智地使用延迟初始化。延迟初始化可能会导致细微的错误,并且并不总是会产生改进的性能来证明增加的复杂性是合理的。不要过早优化。

【讨论】:

  • 是的,Effective Java 是我们的数学原理。另外,我很遗憾我只能为你的答案投票一次。
  • 解释#1:构造函数有很多参数——我认为值得一提的是,有很多构造函数参数可能意味着该类有两个很多依赖项,并且可能试图做太多
【解决方案2】:

最大的缺点可能与编写任何其他长函数相同——它可能变得复杂且难以理解。

其余的会有所不同。首先,长度和执行时间不一定相关——你可能有一行代码(例如,函数调用)需要几秒钟才能完成(例如,连接到服务器)或大量代码完全在CPU,很快就完成了。

启动时间(显然)只会受到启动期间调用的构造函数的影响。在我编写的任何代码中,我都没有遇到过这个问题(无论如何都是最近),但我已经看到了这样的代码。在某些类型的嵌入式系统(例如)上,您确实希望避免在正常使用期间创建和销毁对象,因此您在启动期间几乎静态地创建了所有内容。一旦它运行起来,您就可以将所有的处理器时间用于完成真正的工作。

【讨论】:

    【解决方案3】:

    我会避免在你的构造函数中做任何不是绝对必要的事情。在那里初始化你的变量,尽量不要做太多其他事情。附加功能应驻留在单独的函数中,仅在需要时调用。

    【讨论】:

      【解决方案4】:

      构造函数有点特殊,构造函数中未处理的异常可能会产生奇怪的副作用。在没有看到您的代码的情况下,我会假设较长的构造函数会增加异常的风险。我会根据需要使构造函数尽可能简单,并利用其他方法来完成其余的工作,以提供更好的错误处理。

      【讨论】:

        【解决方案5】:

        如果我们使用复制构造函数,则在这种情况下不要使用引用 它将创建一个对象并调用复制构造函数并传递 复制构造函数的值以及每次创建新对象时 每次它会调用复制构造函数时,它都会进入无限期 填满内存然后显示错误信息。

        如果我们传递引用,它不会创建新的对象来存储 价值。并且不会发生递归

        【讨论】:

          【解决方案6】:

          它自然会影响构造该对象所需的时间,但只不过是有一个空的构造函数并调用方法来完成这项工作。它对应用程序加载时间没有影响

          【讨论】:

            【解决方案7】:

            构造函数是另一个函数。您需要多次调用很长的函数以使程序运行缓慢。因此,如果它只被调用一次,通常里面有多少代码并不重要。

            【讨论】:

              猜你喜欢
              • 2023-03-19
              • 2011-08-07
              • 2019-12-27
              • 2011-11-05
              • 1970-01-01
              • 2011-01-25
              • 1970-01-01
              • 2018-06-23
              • 2011-11-21
              相关资源
              最近更新 更多