This post is the fifth in a series about Poka-yoke Design - also known as encapsulation.

Default constructors are code smells. There you have it. That probably sounds outrageous, but consider this: object-orientation is about encapsulating behavior and data into cohesive pieces of code (classes). Encapsulation means that the class should protect the integrity of the data it encapsulates. When data is required, it must often be supplied through a constructor. Conversely, a default constructor implies that no external data is required. That's a rather weak statement about the invariants of the class.

Please be aware that this post represents a smell. This indicates that whenever a certain idiom or pattern (in this case a default constructor) is encountered in code it should trigger further investigation.

As I will outline below, there are several scenarios where default constructors are perfectly fine, so the purpose of this blog post is not to thunder against default constructors. It's to provide food for thought.

If you have read my book you will know that Constructor Injection is the dominating DI pattern exactly because it statically advertises dependencies and protects the integrity of those dependencies by guaranteeing that an initialized consumer is always in a consistent state. This is fail-safe design because the compiler can enforce the relationship, thus providing rapid feedback.

This principle extends far beyond DI. In a previous post I described how a constructor with arguments statically advertises that the argument is required:

public class Fragrance : IFragrance
{
    private readonly string name;
 
    public Fragrance(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }
 
        this.name = name;
    }
 
    public string Spread()
    {
        return this.name;
    }
}

The Fragrance class protects the integrity of the name by requiring it through the constructor. Since this class requires the name to implement its behavior, requesting it through the constructor is the correct thing to do. A default constructor would not have been fail-safe, since it would introduce a temporal coupling.

Consider that objects are supposed to be containers of behavior and data. Whenever an object contains data, the data must be encapsulated. In the (very common) case where no meaningful default value can be defined, the data must be provided via the constructor. Thus, default constructors might indicate that encapsulation is broken.

When are Default Constructors OK? #

There are still scenarios where default constructors are in order (I'm sure there are more than those listed here).

  • If a default constructor can assign meaningful default values to all contained fields a default constructor still protects the invariants of the class. As an example, the default constructor of UriBuilder initializes its internal values to a consistent set that will build the Uri http://localhost unless one or more of its properties are subsequently manipulated. You may agree or disagree with this default behavior, but it's consistent and so encapsulation is preserved.
  • If a class contains no data obviously there is no data to protect. This may be a symptom of the Feature Envy code smell, which is often evidenced by the class in question being a concrete class.
    • If such a class can be turned into a static class it's a certain sign of Feature Envy.
    • If, on the other hand, the class implements an interface, it might be a sign that it actually represents pure behavior.

A class that represents pure behavior by implementing an interface is not necessarily a bad thing. This can be a very powerful construct.

In summary, a default constructor should be a signal to stop and think about the invariants of the class in question. Does the default constructor sufficiently guarantee the integrity of the encapsulated data? If so, the default constructor is appropriate, but otherwise it's not. In my experience, default constructors tend to be the exception rather than the rule.


Comments

Agreed.
My most common scenario for the default constructor is when some (probably reflection-based) framework requires it.
Don't like them, at all. There are always some dependencies, and excluding them from the ctor means you are introducing them in an uglier place...

Good post!
2011-05-30 16:24 UTC
martin #
So if I have a DTO class(only properties), all the properties should be provided in the contructor or the class should be contructed by some builder ?
2011-05-30 20:06 UTC
One caveat is the case of de- serialization.
2011-05-31 00:32 UTC
Loving this series Mark. Nice work :)
2011-05-31 01:14 UTC
Ken #
I would like to point out if you are using expression blend you need a default constructor on your view otherwise Blend can't open it. Something like this.

public ReturnPolicyManagementView(): base()
{
InitializeComponent();
}
public ReturnPolicyManagementView(ReturnPolicyManagementViewModel model) : this()
{
this.DataContext = model;
}

You can of course get away with this because WPF bindings fail without causing an exception.
2011-05-31 15:29 UTC
Martin, Bob, Ken, I wrote this postscript that essentially explains why such boundary code isn't object-oriented code. At the boundaries, encapsulation doesn't apply.
2011-05-31 18:17 UTC
You have such a unique way of looking at code & constructs, it's eye opening to say the least. I enjoy how you turn the widely accepted into a beast of evil :)

Attacking default constructors is either mad or madly genius.
2011-05-31 20:08 UTC
Nice series. This post reminded me of a painful realization I made lately, which is that by design, a struct must have a parameterless default constructor. Does this make structs evil in your book, except for cases where the parameterless constructor makes sense?
2011-06-05 07:39 UTC
structs aren't evil - as with most other language constructs, it's a matter of understanding when and how to apply them. Basically, a struct is (or should be) a specialized representation of a number. Int32, Decimal, DateTime, TimeSpan etc. all fall into this category, and if you have a similar domain-specific concept in you code base, a struct could be a good representation as long as 0 is a valid value. A hypothetical Temperature type would be an excellent candidate.

An example where a struct was inappropriately applied would by Guid, whose default value is Guid.Empty. Making Guid a struct adds no value because you'd always conceptually have to check whether you just received Guid.Empty. Had Guid been a reference type we could just have checked for null, but with this design choice we need to have special cases for handling exactly Guids. This means that we have to write special code that deals only with Guids, instead of code that just deals with any reference type.
2011-06-05 08:02 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Monday, 30 May 2011 13:02:02 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Monday, 30 May 2011 13:02:02 UTC