by Zoran Horvat
The Builder pattern is useful when there is the process of constructing an object, the process guided by rules and validations. In that case, we choose to introduce a class to encapsulate and enforce the entire process – the class usually referred to as the builder.
This article is coming from the video course Advanced Defensive Programming Techniques which examines design techniques which are dismissing the defensive code. As you will see, the last code sample in this article will be completely free from any kind of defense, even though every step of the process can fail. Keep reading and you will see what it means to develop a defensive design.
Let’s look at an example. We might have a teaching professor, a college subject and a student, and we wish to wrap them together into an object which represents an exam application. The Student would apply for an exam on a Subject, administered by the Professor. The figure below shows relationship between the exam application and the rest of the object model: Exam application binds together the three objects into a meaningful composition.
The problem in constructing the ExamApplication object, which carries a substantial amount of code. Professor can only administer exams on subjects which he is teaching. Student can only apply for an exam on a subject she has been attending and has no passing grade already. We could come up with even more rules than this, and all that would complicate matters beyond reason for a simple constructor. Not to mention that all objects involved would have to be non-null, which also has to be verified.
We don’t like to pile up the validations in a constructor. For one thing, validations might vary across the domain. What might prevent one student from taking an exam, that might not be an obstacle for someone else. Supporting candidates from an international student exchange program, for example, could lead to relaxed rules applying only a subset of students. Such varying logic should simply not be hard-coded in the constructor.
There is another, subtle difficulty which prevents the new ExamApplication object take responsibility to validate the input objects. It still doesn’t exist. If validation is done in the constructor only, then be advised that the constructor cannot really defend. It can only fail. And we don’t need the failure. We wanted to have an ExamApplication instance, and now we must look for a solution that suits the purpose better than the plain constructor.
We can replace the constructor with a static factory function. That would let us return a valid object in a positive case or return a failure indication in case of an error. On the other hand, complicated, and especially varying validation logic is what often makes us choose to apply the Builder design pattern. We would wrap consistency rules into a separate class and call it the ExamApplicationBuilder. This idea is shown in the next diagram.
We still have a Professor, a Subject and a Student. And we want to build a brand new ExamApplication instance to bundle them together. The problem we have faced before was that none of these objects, including the resulting ExamApplication, could be responsible to control the process. Simply, each of the objects had its own narrow responsibilities, and knowing about a bunch of additional rules would be one responsibility too many.
The bright idea behind the Builder patter is this. Construct an empty object, such that it is consistent out of the box. Then make that object start navigating execution in form of a step-by-step process which, in its end, generates a complete and consistent ExamApplication object. This was the Builder.
Now this is something new. ExamApplication’s constructor will not have to fail under any circumstances. All possible issues would have already been checked by the builder object and if we ever arrived at the stage when constructor executes, then we would already know for sure that everything is fine. That is how defensive design can save us from troubles. Operation doesn’t have to fail.
Anyway, not everything is so nice about this idea. Builder may be a heavy axe at times, and you might be reluctant to add it easily to your design. Lucky enough, there is a middle solution.
In the beginning, we had a constructor, as a factory method, which is a lightweight solution in most cases. We also had the Builder as a heavyweight solution, reserved for some other cases. It makes sense then to ask if there is anything in between these extremes?
And there is. We could turn a builder into a series of smaller factories. It’s all about responsibilities – we are thinking about which object would be the natural candidate to encapsulate which validation.
Let’s see. Maybe the Professor could check his own connection with the Subject and construct a smaller object? An object called Exam. This object would only wrap Professor who administers the exam, and the Subject.
Then the Student would come to the picture. A Student object could test eligibility to take the exam, and then, if all the rules were satisfied, produce the ExamApplication object on the output.
Below is the diagram showing this new separation of responsibilities.
What does this redistribution mean to the domain rules we had to validate?
The responsibility of the Professor object is to take the Subject and verify that this professor can administrate an exam on that subject. The simplest rule was that the Professor had to be non-null. And professor will be, or otherwise we wouldn’t be able to invoke a method on it anyway. Then this rule is satisfied by design.
The next rule is that the Professor must be teaching the Subject. That rule would become part of the Professor’s implementation. Professor class could expose a member, such as IsTeaching, which receives the Subject and answers the question to the caller. This method could verify that Subject is non-null. If caller is not certain how to proceed, then it would ask this member for an advice. If the caller has discovered that everything is fine, then it can proceed and call Professor’s method AdministerExam, which receives the subject and produces the exam object on the output.
class Professor
{
public bool IsTeaching(Subject subject) { ... }
public Exam AdministerExam(Subject subject)
{
if (!this.IsTeaching(subject))
throw new ArgumentException();
...
}
}
The next set of rules deals with the student. The Student must be non-null; and it must satisfy additional criteria regarding the Subject. Since we are going to place the next call on the Student object, this rule that the Student must be non-null will be satisfied out of the box as well. I like calling methods on objects for this reason. You don’t have to check whether an object is non-null. The check must have been done long ago, at the point when you obtained the object reference. Beyond that line, you know you’re safe to invoke any member you like.
Anyway, Student would have something to say about the exam. Student could expose some CanTake method, which accepts an Exam. This method would have to defend against a null reference, too. And then it would execute the remaining domain validations, like checking whether the student has been enlisted for the semester in which the subject is taught and whether there is a prior positive grade on the subject or not. If all these rules are satisfied, then the student’s method ApplyFor, which accepts the Exam, would happily produce the ExamApplication object on the output.
class Student
{
public bool CanTake(Exam exam) { ... }
public ExamApplication ApplyFor(Exam exam)
{
if (!this.CanTake(exam))
throw new ArgumentException();
...
}
}
This is the outline of the general design which is based on creating small objects and building larger objects out of them. Each object in the system is coming as a guarantee that all the relevant domain rules have been satisfied, and the ultimate proof that it is so is the fact that you have a reference to an object.
It cannot be much simpler than that. On the example with college exams, the Professor object is a guarantee that there is such a person which is teaching at the college. And Subject instance is then a guarantee that there is such a subject at the college. And then Exam object is the guarantee that such professor is eligible to administer an exam on such a subject. And so, the story goes on with the Student, which is a legitimate person studying at the college. And ExamApplication object is the guarantee that that student can take that exam.
This is what it looks like to build the final object in practice:
Exam exam = professor.AdministerExam(subject);
ExamApplication app = student.ApplyFor(exam);
When this process is over, the ExamApplication object will later be the formal proof that the grade can be assigned to that student on that subject, administered by that professor. You see, nothing to defend, ever, if only you can get hold of an object. All the rules have been encoded into existential preconditions for each of the objects in the system.
This design can be improved even further. The Boolean methods can be removed, and methods that build the objects can return optional objects instead. You can read more about optional objects in previous article titled Custom Implementation of the Option/Maybe Type in C# . Optional objects are very convenient to replace a combination of a Boolean method which guards another method.
Both Professor and Student classes would expose methods which are building optional Exam and ExamApplication objects, respectively. Boolean methods could remain, but their purpose would not be the same anymore. In a more extreme design, these methods can be turned private and only used from inside the methods which are producing optional results. Here is what that design could look like:
class Professor
{
private bool IsTeaching(Subject subject) { ... }
public Option<Exam> AdministerExam(Subject subject) =>
this.IsTeaching(subject)
? new Exam(this, subject)
: None.Value;
}
class Student
{
private bool CanTake(Exam exam) { ... }
public Option<ExamApplication> ApplyFor(Exam exam) =>
this.CanTake(exam)
? new ExamApplication(this, exam)
? None.Value;
}
This design lends itself very nicely on the consuming end, since all objects in the construction process are optional. Every step is free to fail and return None, indicating to subsequent steps that some of the rules has been violated and no exam application can be formed. Here is what it would look like to consume this API:
Option<ExamApplication> app =
professor.AdministerExam(subject)
.MapOptional(exam => student.ApplyFor(exam));
If either the AdministerExam or the ApplyFor method has returned None, the overall result of the operation would be None. Otherwise, if both methods have succeeded, that would be the indication that all validation rules have been obeyed and the resulting ExamApplication would be Some.
If you are not accustomed to think in terms of optional objects, I would strongly advise you to read the article Custom Implementation of the Option/Maybe Type in C# .
In this article, we have tackled the question of constructing valid objects in presence of complex validation rules.
We have started from a simple constructor validation, concluding that it is not a proper place for anything more complex than fixed checks, like null checks. Constructors are not a good place for validation for two reasons. One, constructor cannot indicate that data are invalid. It can only fail. Two, constructor is hardcoded, and it cannot flex and adapt to changing rules.
The next design we have mentioned was the most complex one – the Builder pattern. Builder can hold all the validations and allow the caller to add components one at the time before requesting the complete product to be built in one go. The Builder can also let the caller ask whether the attempted operation will succeed or not, adding a bit to the application’s stability.
After that, we have devoted the major part of the effort to finding the middle solution, one that would fix the shortcomings of constructors, and yet avoid the added cost of introducing a full-blown Builder. In this article, we have proposed a multi-step construction process, where each step is responsible to perform one small chunk of the work and produce one little intermediate object. Intermediate objects are then used to advance one step at the time, until the final result is built.
As a corollary to this idea, we have also shown the idea with methods which are returning optional intermediate objects. This design is useful if you want to wrap positive and negative flows into one method. The overall result of the chain of small building calls would be Some, with the build result wrapped inside, if all the validations have passed well. If any of the validations has failed, the overall result will be None and no instability would be caused during the process.
This whole technique with small intermediate objects is part of the defensive design technique. I find much more value in that technique than in any piece of explicit defensive code.
You can learn more about defensive design from my Pluralsight course Advanced Defensive Programming Techniques . That course is covering wide range of defensive designs which are meant to dismiss any explicit defensive code. Avoiding Builders and replacing them with a multi-step process was one of the techniques designed to improve application stability.
If you wish to learn more, please watch my latest video courses
In this course, you will learn the basic principles of object-oriented programming, and then learn how to apply those principles to construct an operational and correct code using the C# programming language and .NET.
As the course progresses, you will learn such programming concepts as objects, method resolution, polymorphism, object composition, class inheritance, object substitution, etc., but also the basic principles of object-oriented design and even project management, such as abstraction, dependency injection, open-closed principle, tell don't ask principle, the principles of agile software development and many more.
More...
In this course, you will learn how design patterns can be applied to make code better: flexible, short, readable.
You will learn how to decide when and which pattern to apply by formally analyzing the need to flex around specific axis.
More...
This course begins with examination of a realistic application, which is poorly factored and doesn't incorporate design patterns. It is nearly impossible to maintain and develop this application further, due to its poor structure and design.
As demonstration after demonstration will unfold, we will refactor this entire application, fitting many design patterns into place almost without effort. By the end of the course, you will know how code refactoring and design patterns can operate together, and help each other create great design.
More...
In four and a half hours of this course, you will learn how to control design of classes, design of complex algorithms, and how to recognize and implement data structures.
After completing this course, you will know how to develop a large and complex domain model, which you will be able to maintain and extend further. And, not to forget, the model you develop in this way will be correct and free of bugs.
More...
Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.