开发者

Constructor: Full fledged or minimal?

When designing classes you usually have to decide between:

  • 开发者_开发技巧providing a "full" constructor that takes the initial values for all required fields as arguments: clumsy to use but guarantees fully initialized and valid objects
  • providing just a "default" constructor and accessors for all necessary fields: might be convenient sometimes but does not guarantee that all members are properly initialized before some critical methods are called.
  • a mixed approach (more code, more work, can' eliminate the "not fully initialized" problem)

I have seen several APIs and frameworks that use one of the above or even an inconsistent approach that differs from class to class. What are your thoughts and best practices on that subject?


The short answer is that an object should be fully initialized after its constructor was called.

This should be the default approach with the least surprises for users. There are cases when your run-time, framework or other technical constraints prevent the default approach.

In some circumstances a the Builder pattern helps to support cases when it is not possible to use a simple constructor. This approach is in the middle ground, letting users call setters to initialize and still be able to work only with fully initialized objects.

A static factory method is appropriate in cases when the object constructor needs to be more flexible than a constructor but a builder is too complex to implement.

Constructor:

x = new X(a, b);

Setter:

x = new X();
x.setA(a);
x.setB(b);

Builder:

builder = new Builder();
builder.setA(a);
builder.setB(b);
x = builder.build();

Static factory method:

x = X.newX(a, b);

All four approaches will produce an instance, x, of class X.

Constructor

Pros:

  • Simple

Cons:

  • May be not possible due to constraints
  • May need too many constructor arguments (named arguments and default values can help here if the languages supports them: new X(a = "a", b = "c"))

Setter

Pros:

  • Moderate complexity
  • May be mandated by framework

Cons:

  • Instances may not be fully initialized

Builder

Pros:

  • Most flexible approach
  • May reuse instances (using singletons, flyweights and caches internally)

Cons:

  • Most complex to implement
  • May produce run-time exceptions for invalid initialized objects
  • Overhead of the builder object instance

Static factory method

Pros:

  • Moderate complexity
  • May reuse instances (using singletons, flyweights and caches internally)

Cons:

  • More complex to implement and use than constructor


Definitely "full" in all but the edgiest of edge cases.

It's very straightforward to me, that the point of a constructor is to set up the object so that it's ready to use. Obviously immutable objects are by far the best, but it's also often acceptable for some types of object to change state later. One thing to avoid, however, is having on object that can be constructed at one point, but an init() or setup() method must be called on it before use. It's irritating, confusing - and what's the point of having an object constructed if it's illegal to call "real" methods on it?

To my mind there are no substantial downsides to requiring "full" construction; the callers would have to marshall all the required arguments together before being able to use the object anyway, and making them do so in the constructor removes a whole class of errors. It's great to know that if you're passed an instance of an object it will be valid for use, rather than having some temporal and/or state dependency. If anything, this can also make it clearer to calling code exactly what dependencies need to be gathered by having them all defined in one clear place.

In fact the only concrete argument I can see for not doing this is circular dependencies; if class A requires a B and vice versa, then one or other of these relationships must be fulfilled via a setter method. Whether this represents good design is left as an exercise for the reader. :-)


I would say that full constructors are the way to go, not only for consistency (as you say, fully initialization of the object is guaranteed after the object is created), but also because it eases the work when having to use a dependency injection mechanism.


I have a list of rules I use for myself:

  • An instance after construction should be valid -- it follows that my constructor with minimal parameters will have as less of them as essentially required;
  • There should be a constructor allowing to initialise as much of parameters as practically possible;
  • Others constructors with sensible subset of parameters.

So this is voting for mixed approach with overloaded constructors. But the most important for me is that a constructed object should be either valid after construction or exception will be thrown, otherwise it is too error-prone to allow "not fully initialized" or invalid objects.


My thoughts are:

  • More than about 4-5 constructor arguments tends to be confusing, hard-to-read and otherwise inadvsiable;
  • Immutable objects should be favoured over mutable objects. This often necessitates large constructors;
  • If necessary create "builder" objects (using fluent interfaces) that are mutable but spit out an immutable object at the end;
  • If your language supports it, anonymous objects (like in Javascript) are a great way of supply many arguments to a constructor (or any function for that matter).


This is a very generic question, so I can only give you a very generic answer. There's a huge difference between writing internal code, and code that will be published as an API for other developers.

I think for the latter case (which I believe is what you're interested in) I would go for a very minimal approach. If you need to supply a ton of values, I would even argue you might even try to stick too much functionality in 1 single class.

But if the user is most likely going to customize all values every time, then by all means force them to supply the values. If there are a lot of sensible values possible.. make sure you use these defaults.

Lastly, if you're classes are often going to be subclassed by the user, you might want to avoid constructor arguments altogether. (speaking from a PHP background, where we don't have method overloading).


No. (You shouldn't use one or the other - you should use one, the other, OR the mixed approach :)

The issue is when should you use each?

1) Are there a few well-defined use-cases? Or are there millions?

Example A: An Address object that always needs to have certain fields initialized. Use full-fledged constructor.

Example B: An Address object that needs to have certain fields intiialized, others are optional. Use mixed approach.

Example C: A graphics object that has zillions of parameters that may or may not be set, and is called in a different way by every program that uses it. Use a full constructor for some required fields (if any) but mostly rely on methods/properties to set the fields.


The guys at Qt Software have had a good think about these types of questions: http://qt.gitorious.org/qt/pages/ApiDesignPrinciples


Always fully initialise before use without requiring the user of the class to do anything but construct and use the object.

Only delay full initialisation to the first call of a normal member function if performance profiling dictates.

Only use two stage initialisation (i.e. user MUST construct and then MUST call a custom Init() function before using the object for real) where this is the ONLY way to achieve initialisation.

If you allow the users of your code to create incomplete objects and require a certain sequence of method calls to make the object safe to use then you're just building bugs for the future.


"using constructor to populate data fields" is a faster way than "default constructor + assign data to fields manually"

But always keep in mind as a good practice a method (also a constructor) must not have more than 6-7 parameters.


Full constructor, unless for some reason you're trying to avoid using exceptions in your code base.

I'm not going to go into the arguments for and against banning all exceptions, but for instance in C++ if you're not using nothrow new everywhere, then you aren't avoiding all exceptions, so the special case doesn't apply.

Getting back into more language-agnostic territory, you might argue that "exceptions are for exceptional cases only", but also that "this constructor failing is not exceptional". I'm not going to go into the arguments for or against that, either, but it leaves you with two options:

1) Set a "failure" flag on the object if construction fails, and callers have to check it (either explicitly, or it's checked in other functions which behave differently according to whether it's set). C++ file streams do this with the constructor that takes a filename. It's a nuisance for the caller, but less of a nuisance than two-stage construction.

2) Have a helper object to perform the operations which can fail but which should not be exceptions, and make callers use this. That is, replace:

MyObj(a,b,c); // might throw

with

MyArgs args(a,b,c);
// optionally, if caller doesn't want an exception
if (!args.ok()) handle_the_error();
MyObj(args);

Then in the constructor that takes a MyArgs, MyObj can also call args.ok(), and throw an exception if they aren't. One class could offer both constructors (or factory methods if your language doesn't allow multiple constructors), and let the caller decide.

Fundamentally, if you want to avoid an exception, then the caller is going to have to manually check for success somewhere. Personally I think that if the reason for failure amounts to bad arguments, then it's better to check beforehand. Obviously file streams can't do that, because whether the argument is "bad" or not can change if the filesystem is modified between the check and the constructor. So you have no choice but to check afterwards. This is still better than two-stage construction.

If you absolutely must have two-stage construction of the object itself, then I think it's best to hide that in a factory method. Assuming your language allows some kind of "null" to indicate errors, that is.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜