Pros and Cons of Multiple Constructors

by Zoran Horvat
Dec 19, 2017

Understanding Limitations of Constructors

One drawback often emphasized about constructors is that they don’t have their own names. Constructors are named after a class, and that doesn’t necessarily communicate their logic well.

As an example, look at the very simple Student class implementation, which is currently only responsible to keep track of the student’s name and to let her enroll for a semester. The latter feature will impact the way we instantiate this class.

public class Student
{
    public string Name { get; }
    public Semester Enrolled { get; private set; }

    public Student(string name)
    {
        this.Name = name
            ?? throw new ArgumentNullException(nameof(name));
    }

    public void Enroll(Semester semester) { ... }
}

And then comes the domain logic. Students can enroll a semester and then take exams at some later time. The trick is that students must enroll semesters in order. Any attempt to jump over semesters will result in an exception.

Here is the implementation:

public class Student
{
    public string Name { get; }
    public Semester Enrolled { get; private set; }
    ...
    public void Enroll(Semester semester)
    {
        if (semester == null || semester.Predecessor != this.Enrolled)
            throw new ArgumentException();
        this.Enrolled = semester;
    }
}

And just after successfully unrolling this change, the new requirement comes to the table, telling that some students may come from a student exchange program. These students don’t have to enroll semesters in order. We get an idea (not a bright one!) to constrain Student instances by keeping a Boolean flag, which tells whether the student comes from exchange or not. The new flag has suddenly become a constructor argument.

public class Student
{
    public string Name { get; }
    public Semester Enrolled { get; private set; }
    private bool ComesFromExchange { get; }

    public Student(bool comesFromExchange, string name)
    {
        this.ComesFromExchange = comesFromExchange;
        this.Name = name;
    }
    ...
}

Don’t judge my design yet, I’m just trying to find shortest path out from this new requirement. On the bright side, this flag is private, and nobody can really tell its value from just looking at the object of this class. Why exposing it through the public constructor then?

Introducing Factory Functions in Place of Constructors

One common technique to improve communication skills of the constructor is to replace it with one or more static factory methods. We can turn the constructor private, as the first step. Nobody will see it anymore, and the only way to construct the Student instance will be from the inside.

In that light, I will allow the caller to construct the object through static factory methods, but I will only offer limited options. Static factories will communicate special meanings about the construction process. Like having a static Create method, which communicates nothing special, and therefore makes us believe that it will construct a common student. Another method, CreateFromExchange, will clearly communicate additional information through its name. The name says that it will create the student coming from an exchange program.

public class Student
{
    ...
    // Mind the private constructor
    private Student(bool comesFromExchange, string name) { ... }

    public static Student Create(string name) =>
        new Student(false, name);
    public static Student CreateFromExchange(string name) =>
        new Student(true, name);
}

I am showing you this as an example how you can make your code more readable and more understandable. You should never underestimate the power of code readability. Knowing precisely what the function will do by just looking at its signature, and without having to look at its source to confirm your expectations, is a great defensive tool. These two factory functions are telling their purpose quite obviously.

Understanding Drawbacks of Multiple Creation Methods

On the other hand, this solution with static factory methods is bringing troubles on another plane. If you have multiple methods to construct a single object, then it may look like your object is doing different things, depending on how it was constructed. And that is true for this Student class. Boolean flag will affect the way it executes the Enroll method, for example:

public class Student
{
    ...
    public void Enroll(Semester semester)
    {
        if (semester == null ||
             (!this.ComesFromExchange &&
              semester.Predecessor != this.Enrolled))
            throw new ArgumentException();
        this.Enrolled = semester;
    }
}

A Boolean flag inside an object is a bad coding practice, and it calls for defensive code in all other places. Callers will probably need to think how to deal with an object depending on the method which was used to construct it. In this case, caller would need to know whether the student comes from exchange program or not, before attempting to enroll her for a semester. Failing to check conditions up-front will lead to an exception thrown back from the Enroll method.

Beware of that coding style. If you catch yourself making more than one factory method for one class, you better ask yourself is it really one class you’re talking about? Maybe you’re talking about two classes, only merged together into one piece of code.

Introducing Subclasses with One Constructor Each

My advice is to have exactly one factory function per class, and have no discrete parameters, such as Booleans and enums. In the remainder of this article, I will show you one way of resolving this issue with students. It is based on class derivation. There are other ways, based on object composition, strategies, etc. and I will suggest you investigate alternatives as an exercise.

If I wanted to have two kinds of students, regular ones and those coming from student exchange program, then I could make two derived classes, RegularStudent and ExchangeStudent, both deriving from the base Student. Each of these new classes will expose only one factory function, in form of a single parameterized constructor. And then the base type will be relieved from having to know where the student is coming from.

public abstract class Student
{
    public string Name { get; }
    public Semester Enrolled { get; set; }

    protected Student(string name)
    {
        this.Name = name;
    }

    public abstract bool CanEnroll(Semester semester);

    public void Enroll(Semester semester)
    {
        if (!this.CanEnroll(semester))
            throw new ArgumentException();
        this.Enrolled = semester;
    }

}

public class RegularStudent : Student
{
    public RegularStudent(string name) : base(name) { }

    public override bool CanEnroll(Semester semester) =>
        semester != null && semester.Predecessor == base.Enrolled;
}

public class ExchangeStudent : Student
{
    public ExchangeStudent(string name) : base(name) { }

    public override bool CanEnroll(Semester semester) =>
        semester != null;
}

Not only that the base type doesn’t know the details of the student, but it will not be responsible to construct objects at all. Both static factory functions that used to reside in the Student class are now gone. Information is encoded in the subtype itself.

And then we come to the feature which used to depend on construction method. Enroll method has to check the rules before proceeding. That is where abstract CanEnroll method comes to the picture, letting the derived classes to implement precise domain logic in each of the cases separately.

In particular, regular student will only allow enrolling the immediately following semester. And student coming from exchange will be allowed to enroll any semester with no constraints (other than the trivial request to have a non-null semester reference, of course.)

Summary

This completes implementation of the feature which used to depend on the way in which student instances are constructed. I will strongly advise you to avoid multiple creation methods for any class. Instead, you will probably want to construct several related classes, each covering one usage scenario, and each instantiated in one and only one way.

Allowing multiple construction methods may reflect on stability in negative ways, and that is the primary issue I have tried to fight by introducing derived classes in this example. On the other hand, if you decide to keep multiple constructors in place, then you might opt to replace them with static factory functions, for the sake of improved code readability.