Practical Patterns: Control with Builder

Builder-Director

There are many situations where one needs to construct a complex object before consuming it. You might need to pass many arguments, set configurations and many other mandatory properties before the instance is ready for use. Passing parameters through constructors is a sure way of ensuring that your objects are always in a proper state when created. But for complex objects having everything through constructors can be a bit cumbersome and restrictive too in some cases.

I will not be taking you through building the standard builder pattern, but one which implements a fluent interface to build a product. It would not just be chaining of methods, but will also ensure that domain rules are met for the product.

A quick review of the main features of the builder pattern itself before we start addressing its quirks and the alternate approach.

A builder always returns a concrete product unlike factory or abstract factory which return abstract products.

A builder encapsulates the construction of an object (product). You would use one when the process of constructing an object is complex. The builder only defines the steps required to construct the object.

It does not enforce any order in which they should be called. The director has to know this and constructs the object through the builder.

While the builder is being built, it is in a mutable state.

Let us consider Account as our product which requires to be built.

1
2
3
4
5
6
7
8
9
class Account
{
public int Number { get; internal set; }
public string Type { get; internal set; }
public IContact PrimaryContact { get; internal set; }
public string HomeBranch { get; internal set; }
// Optional
public IEnumerable<IContact> OtherContacts { get; internal set; }
}

We have IContact just to spice up this product.

1
2
3
interface IContact
{
}

Let us also define a concrete for this interface

Our typical builder interface would look something like this

1
2
3
4
5
6
7
8
interface IAccountBuilder
{
void SetNumber(int number);
void SetType(string type);
void AssignHomeBranch(string branchName);
void RecordPrimaryContact(IContact primary);
Account Build();
}

Most of the examples of the Builder out there would guide you to build a Director next, which takes in a Builder and returns the product. The director should now the order in which to call the methods in the builder. Most of the Directors do not accept any input from the user except the Builder itself. But if we need to accept inputs from the user (or program) to create our Product, the Builder would need to accept the inputs (as in our example) and the Director would also need to take in the inputs so that it could pass it over to the builder.

Instead we can have a Builder something like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class AccountBuilder
{
private Account _account = null;
private List<IContact> Contacts {get; set;} = new List<IContact>();

public AccountBuilder Create()
{
_account = new Account();
_account.OtherContacts = Contacts;
return this;
}

public AccountBuilder As(string type)
{
_account.Type = type;
return this;
}

public AccountBuilder With(int number)
{
_account.Number = number;
return this;
}

public AccountBuilder In(string branch)
{
_account.HomeBranch = branch;
return this;
}

public AccountBuilder HavingAddress(IContact contact)
{
_account.PrimaryContact = contact;
return this;
}

public AccountBuilder AddingOtherAddress(IContact contact)
{
Contacts.Add(contact);
return this;
}

public Account Build()
{
return _account;
}
}

We can now create an Account instance like this

1
2
3
4
5
6
7
AccountBuilder _builder = new AccountBuilder();
var account = _builder.Create()
.As("Saving")
.With(123)
.HavingAddress(new Address("Sydney"))
.In("Sydney")
.Build();

The above Builder uses chaining of methods which constructs an Account class. Each method returns the same copy of its instance, allowing chaining of methods. Through this we can assign properties of the Account class. Finally, calling the Build() returns us the completed(hopefully!) Account class.

Even though the new builder is much easier to use, it comes with a lot of pitfalls. In order to use this interface, the developer would need to know the implementation details of each method. We cannot enforce assigning of mandatory properties. Knowing which are optional is also not intuitive. The client can also repeat assigning certain properties.

Let us try to remove some of the ways to fail by introducing certain guards. How can we ensure we always start with Create(), which allows for the instance of Account to be initialised.
We’ll make the constructor of AccountBuilder private and make the Create() a static method. The clients can now access AccountBuilder only through Create().

1
2
3
4
5
6
7
8
9
10
// In AccountBuilder
private AccountBuilder()
{
var _account = new Account();
_account.OtherContacts = Contacts;
}
public static AccountBuilder Create()
{
return new AccountBuilder();
}

Now our clients would use the builder like this

1
2
3
4
5
6
var account = AccountBuilder.Create()
.As("Saving")
.With(123)
.HavingAddress(new Address("Sydney"))
.In("Sydney")
.Build();

Now the only saving grace is that we can at least ensure the client always gets an Account instance, even if they may not be completely constructed. I would still be hesitant to ship this to developers. What we want is to enforce an order of construction and guide them until all the rules have been met before the Account product can be returned.

For this we can look at I of the SOLID principles. Through Interface Segregation Principle, I’ll show how we can have a water tight builder.
We shall isolate each method in a separate interface. While at this, we’ll also determine what should be the next possible call(s) from this method. This can be done by returning the next permissible interface.
Let us see this in code to make it more clear.

Based on our methods we’ll define the following interfaces.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface IAccountType
{
IAccountNumber As(string type);
}
interface IAccountNumber
{
IPrimaryContact With(int number);
}
interface IPrimaryContact
{
IOtherContact HavingAddress(IContact contact);
}
interface IOtherContact
{
IOtherContact AddingOtherAddress(IContact contact);
IHomeBranch NoMoreAddress();
}
interface IHomeBranch
{
IAccountBuilder In(string branch);
}
interface IAccountBuilder
{
Account Build();
}

Each interface returns only the next available interface. This allows the user to progress only through the steps which we have defined. We can also allow the user to skip optional parameters (check IOtherContact). Only when the client reaches IAccountBuilder will they be able to access Build() which finally returns Account instance.

The account builder will now look like this. It will implement all of the above interfaces.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class AccountBuilder : IAccountBuilder, IAccountNumber, IAccountType, 
IPrimaryContact, IHomeBranch, IOtherContact
{
private Account _account = null;
private List<IContact> Contacts {get; set;} =
new List<IContact>();
private AccountBuilder()
{
var _account = new Account();
_account.OtherContacts = Contacts;
}

public static IAccountType Create() => new AccountBuilder();

public IAccountNumber As(string type)
{
_account.Type = type;
return this;
}
public IPrimaryContact With(int number)
{
_account.Number = number;
return this;
}
public IOtherContact HavingAddress(IContact contact)
{
_account.PrimaryContact = contact;
return this;
}
public IOtherContact AddingOtherAddress(IContact contact)
{
Contacts.Add(contact);
return this;
}
public IHomeBranch NoMoreAddress()
{
return this;
}
public IAccountBuilder In(string branch)
{
_account.HomeBranch = branch;
return this;
}
public Account Build()
{
return _account;
}
}

And we use it similar to before

1
2
3
4
5
6
7
account = AccountBuilder.Create()
.As("Saving")
.With(123)
.HavingAddress(new Address("Sydney"))
.NoMoreAddress()
.In("Sydney 2000")
.Build();

With optional address

1
2
3
4
5
6
7
8
account = AccountBuilder.Create()
.As("Saving")
.With(123)
.HavingAddress(new Address("Sydney"))
.AddingOtherAddress(new Address("Katoomba"))
.NoMoreAddress()
.In("Sydney")
.Build();

Doesn’t look much different, but the client cannot progress to the next method unless they go through the previous.
Depending on how you see this, this class is not immutable. If this is something you desire, then do not return the same instance of AccountBuilder, but a new one each time. You would need to maintain the state internally of AccountBuilder. Also create the Account only in the Build() method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class AccountBuilder : IAccountBuilder, IAccountNumber, IAccountType,
IPrimaryContact, IHomeBranch, IOtherContact
{
// Maintain state
private List<IContact> Contacts { get; set; }
= new List<IContact>();
private string Type { get; set; }
private int Number { get; set; }
// protect instantiation
private AccountBuilder()
{}

public static IAccountType Create() => new AccountBuilder();

public IAccountNumber As(string type) => new AccountBuilder()
{
Type = type
};

public IPrimaryContact With(int number) => new AccountBuilder()
{
Type = this.Type,
Number = number
};

public Account Build()
{
var account = new Account();
account.Type = Type;
account.Number = Number;
return account;
}

/// rest of the implementation left out for brevity
}

I have left rest of the methods out in the above example, but you should be able to get an idea out of it. I now internally maintain state of the user’s inputs in the AccountBuilder‘s instance. When the Build() is called, I transfer all the values to the Account instance. BTW, I have used C# 6’s feature of expression-bodied functions.

I find builders very useful, especially when my domain is complex and need to control how the instance is created. With fluent interfacing and enforcing domain rules, it becomes easy for developers to create instances of your product. I find implementation of Builder this way more expressive and with more control.