什么是确定构造函数,初始化和重置方法的最佳实践

问题描述:

尽管我使用Java进行设计,但这是一个普通的OOP问题。我并没有试图解决一个特定的问题,只是想通过一些设计原则。
从我的经验,我已经达到了习惯分离对象设置为三个阶段。什么是确定构造函数,初始化和重置方法的最佳实践

目标是最大限度地减少:额外的工作,混淆的代码和残缺的可扩展性。


建设

  1. 必要 最小的行动创建一个有效的对象,传递一个 存在测试
  2. 实例化和初始化只是“一次性”,从未将超过缠身,非变量对象不会改变/改变对象的生命
  3. 初始化最终成员
  4. Es sentially运行时存根

初始化

  1. 使有用
  2. 实例化对象并初始化可公开访问的成员
  3. 实例化和初始化的私有成员是变量值
  4. 对象应该现在通过外部测试而不产生异常(假设代码是正确的)

复位

  1. 不会实例什么
  2. 分配缺省值到所有变量的公共/私有成员
  3. 返回对象到一个确切的状态

一个玩具例子:

public class TestObject { 
    private int priv_a; 
    private final int priv_b; 
    private static int priv_c; 
    private static final int priv_d = 4; 

    private Integer priv_aI; 
    private final Integer priv_bI; 
    private static Integer priv_cI; 
    private static final Integer priv_dI = 4; 

    public int pub_a; 
    public final int pub_b; 
    public static int pub_c; 
    public static final int pub_d = 4; 

    public Integer pub_aI; 
    public final Integer pub_bI; 
    public static Integer pub_cI; 
    public static final Integer pub_dI = 4; 

    TestObject(){ 
     priv_b = 2; 
     priv_bI = new Integer(2); 
     pub_b = 2; 
     pub_bI = new Integer(2); 
    } 

    public void init() { 
     priv_a = 1; 
     priv_c = 3; 
     priv_aI = new Integer(1); 
     priv_cI = new Integer(3); 

     pub_a = 1; 
     pub_c = 3; 
     pub_aI = new Integer(1); 
     pub_cI = new Integer(3); 
    } 

    public void reset() { 
     priv_a = 1; 
     priv_c = 3; 
     priv_aI = 1; 
     priv_cI = 3; 

     pub_a = 1; 
     pub_c = 3; 
     pub_aI = 1; 
     pub_cI = 3; 
    } 
} 
+2

公共实例变量破坏封装。保持你的私人部分给你自己,下一个维护你的代码的人将会感激它。 – 2009-07-21 02:45:39

不能说我曾经使用过这个确切的模式,但我使用类似的东西来减少代码重复。例如,当有一个对象可以通过构造函数创建,或者通过工厂方法从另一个对象(如DTO)创建时。在这种情况下,我经常会有一个内部初始化程序来填充两者都使用的对象的属性。不能说我曾经使用过一种“重置”方法,如果它只是复制创建一个新对象的过程,我也不会看到真正需要一个方法。

最近,我已经转移到只使用默认构造函数并使用属性设置来初始化对象。新的C#语法允许以“类似构造器”的格式轻松实现这一点,这使得每一个都很容易使用,我发现需要支持参数化构造函数消失。

+0

我认为重置是它将对象返回到原始状态的一种方式,并且该状态是关于数据,信息的排列,更接近状态机而不是正在运行的程序的“状态”。 – Paxic 2009-02-27 00:07:49

我来自C++背景,其规则与Java有点不同,但我认为这些两阶段初始化原则适用于一般情况。

建设是可以在施工时间完成

  1. 一切都应该被施工时间内完成。
  2. 在调用init()之前,尽量减少可能因尝试使用对象而导致的“不良”的数量。
  3. 所有成员变量都需要一个值,即使它是一个通常无效的sentry值(例如,将指针设置为null)。我认为Java会默认初始化所有的变量为零,所以如果这是一个有效的数字,你需要选择其他的东西。

初始化

  1. 初始化依赖于其他对象的存在的那些成员变量。基本上,做你在施工时无法做到的事情。
  2. 确保您的对象现在处于完整的即用状态。如果不是,请考虑抛出异常。

复位

  1. 想想漫长而艰难的是什么状态的系统将在您何时需要调用这样的功能。即使该操作看起来很昂贵,从零开始创建一个新对象也许会更好。分析您的代码以确定是否存在问题。
  2. 假设你已经过了第1项,请考虑编写一个方法来处理reset()和你的构造函数都需要做的事情。这便于维护并避免代码重复。
  3. 将您的对象返回到init()之后的相同状态。

我会以某种方式设计我的类,这样就不需要“init”方法。我认为类的所有方法,特别是公共方法,应该保证对象在成功完成后总是保持“有效”状态,并且不需要调用其他方法。

同样适用于构造函数。当一个对象被创建时,它应该被认为是初始化的并且可以被使用(这是构造函数的目的,并且有许多技巧可以实现这一点)。否则,您可以安全地使用它的唯一方法是检查对象是否在每个其他公共方法的开始处被初始化。

是否有理由init()reset()需要不同?在这个简单的例子中很难看出为什么“不实例化”规则很重要。

除此之外,我认为物体一旦建成就应该有用。如果有一个原因 - 一些循环依赖或继承问题 - 一个对象被构建后“初始化”,我会隐藏的构造函数和初始化背后的静态工厂方法。 (并且可能将初始化代码移动到单独的配置器对象以获得更好的度量。)

否则,你指望呼叫者总是调用构造函数和init(),这是一个非标准模式。我们在这里有一些旧的,非常有用的代码,这是一个抽象的对话类,发生的是,每当有人扩展它时,他们忘记调用constructUI(),然后他们浪费了15分钟,想知道为什么他们的新对话框是空的。

有趣。如果你有一个需要执行IO操作的对象,我尤其会发现这个构造很有用。我不希望任何东西直接或间接地在其构造函数中执行IO操作。它使对象成为一个噩梦使用。