What are the best practices for determining the ta

2019-02-25 16:11发布

问题:

This is a general OOP question although I am designing in Java. I'm not trying to solve a particular problem, just to think through some design principles.
From my experience I have reached the habit segregating object setup into three phases.

The goal is to minimize: extra work, obfuscated code and crippled extensibility.


Construction

  1. The minimal actions necessary to create a valid Object, passes an existence test
  2. Instantiate and initialize only "one time", never to be over-ridden, non variable objects that will not change/vary for the life of the Object
  3. Initialize final members
  4. Essentially a runtime stub

Initialization

  1. Make the Object useful
  2. Instantiate and initialize publicly accessible members
  3. Instantiate and initialize private members that are variable values
  4. Object should now pass external tests with out generating exceptions (assuming code is correct)

Reset

  1. Does not instantiate anything
  2. Assigns default values to all variable public/private members
  3. returns the Object to an exact state

A Toy example:

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

回答1:

I come from a C++ background where the rules are a bit different from Java, but I think these two-stage initialization principles apply in the general case.

Construction

  1. Everything that can be done at construction time should be done at construction time.
  2. Minimize the amount of "badness" that can result from trying to use your object before calling init().
  3. All member variables need a value, even if it's a normally invalid sentry value (e.g., set pointers to null). I think Java will initialize all variables to zero by default, so you need to pick something else if that's a valid number.

Initialization

  1. Initialize those member variables that depend on the existence of other objects. Basically, do the things you couldn't do at construction time.
  2. Ensure your object is now in a complete, ready-to-use state. Consider throwing an exception if it isn't.

Reset

  1. Think long and hard about what state The System will be in when you'd want to call such a function. It may be better to create a new object from scratch, even if that operation seems expensive. Profile your code to find out if that's a problem.
  2. Assuming you got past item 1, consider writing a method to handle the things that both reset() and your constructor need to do. This eases maintenance and avoids code duplication.
  3. Return your object to the same state it was in after init().


回答2:

I would design my classes in a way so that an "init" method is not required. I think that all methods of a class, and especially public methods, should guarantee that the object is always left in a "valid" state after they complete successfully and no call to other methods is required.

The same holds for constructors. When an object is created, it should be considered initialized and ready to be used (this is what constructors are for and there are numerous tricks to achieve this). Otherwise, the only way that you can safely use it is checking if the object has been initialized in the beginning of every other public method.



回答3:

Can't say that I've ever used this exact pattern, but I've used similar things to reduce code duplication. For example when you have an object that may be either created via a constructor or from a another object (like a DTO) via a factory method. In that case, I'll often have an internal initializer that populates the properties of the object that is used by both. Can't say that I've ever used a "reset" method, nor do I see a real need for one if all it does is replicate the process of creating a new object.

Lately, I've moved to only using default constructors and using property settors to initialize the object. The new C# syntax that allows to easily do this in a "constructor-like" format makes this every easy to use and I find the need to support parameterized constructors disappearing.



回答4:

Is there a reason init() and reset() need to be different? It's hard to see in this simple example why the "does not instantiate anything" rule is important.

Beyond that, I think objects should be useful as soon as they're constructed. If there's a reason -- some circular dependency or inheritance issue -- an object has to be "initialized" after construction, I would hide the constructor and the initialization behind a static factory method. (And probably move the initialization code to a separate configurator object for good measure.)

Otherwise you're counting on callers always to call both the constructor and init(), and that's a non-standard pattern. We have some old, too-useful-to-throw-away code here that does that; it's an abstract dialog class, and what happens is, every time someone extends it, they forget to call constructUI() and then they waste 15 minutes wondering why their new dialog is empty.



回答5:

Interesting. I especially find this construction useful if you have an object that needs to perform IO operations. I do not want anything to perform IO operations direct or indirectly in their constructor. It makes the object a nightmare to use.