2

尽管我正在使用 Java 进行设计,但这是一个通用的 OOP 问题。我不是试图解决一个特定的问题,只是想通过一些设计原则。
根据我的经验,我已经养成了将对象设置分为三个阶段的习惯。

目标是尽量减少:额外的工作、混淆的代码和削弱的可扩展性。


建造

  1. 创建有效对象所需的最少操作,通过存在测试
  2. 仅“一次”实例化和初始化,永远不会被覆盖,非变量对象在对象的生命周期内不会改变/变化
  3. 初始化最终成员
  4. 本质上是一个运行时存根

初始化

  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;
   }  
}
4

5 回答 5

2

我会以一种不需要“init”方法的方式设计我的类。我认为一个类的所有方法,尤其是公共方法,都应该保证对象在成功完成后始终处于“有效”状态,并且不需要调用其他方法。

构造函数也是如此。创建对象时,应将其视为已初始化并准备好使用(这就是构造函数的用途,并且有许多技巧可以实现此目的)。否则,您可以安全使用它的唯一方法是检查对象是否已在所有其他公共方法的开头进行了初始化。

于 2009-06-29T11:01:13.843 回答
1

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

建造

  1. 施工时能做的事,应该在施工时做。
  2. 在调用init().
  3. 所有成员变量都需要一个值,即使它通常是无效的哨兵值(例如,将指针设置为空)。我认为默认情况下Java会将所有变量初始化为零,因此如果这是一个有效数字,您需要选择其他内容。

初始化

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

重置

  1. 当您想要调用这样的函数时,请仔细考虑系统将处于什么状态。从头开始创建一个新对象可能会更好,即使该操作看起来很昂贵。分析您的代码以了解这是否是一个问题。
  2. 假设您通过了第 1 项,请考虑编写一个方法来处理reset()您和您的构造函数都需要做的事情。这简化了维护并避免了代码重复。
  3. 将您的对象返回到它在之后的相同状态init()
于 2009-05-05T17:02:51.337 回答
0

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

最近,我转向只使用默认构造函数并使用属性设置器来初始化对象。新的 C# 语法允许以“类似构造函数”的格式轻松执行此操作,这使得它非常易于使用,我发现支持参数化构造函数的需求正在消失。

于 2009-02-26T23:57:57.057 回答
0

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

除此之外,我认为对象一旦被构造出来就应该是有用的。如果有一个原因——一些循环依赖或继承问题——一个对象必须在构造后“初始化”,我会将构造函数和初始化隐藏在静态工厂方法后面。(并且可能将初始化代码移动到一个单独的配置器对象以进行良好的测量。)

否则,您指望调用者总是调用构造函数和init(),这是一种非标准模式。我们这里有一些旧的、太有用的、无法丢弃的代码可以做到这一点;这是一个抽象对话框类,发生的情况是,每次有人扩展它时,他们都会忘记调用constructUI(),然后他们浪费了 15 分钟想知道为什么他们的新对话框是空的。

于 2009-07-31T13:57:12.663 回答
0

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

于 2009-11-25T16:03:55.553 回答