12
\$\begingroup\$

I need to initialize several parameters of an object but I also wanted to make it immutable. At the same time I don't want the constructor to take all several parameters because some of them are optional and have default values that the user can change during initialization like an IReadOnlyDictionary so I was experimenting with different patterns.

I found out that there is an interesting fact that if you make the builder nested inside the actual immutable class it then has still access to its private setters. This means you can build the actual object using the actual object without having to create copies in the builder itself and it still is immutable after exporting it, isnt't it?

This is how it looks like:

class Foo
{
    private IDictionary<string, string> _corge = 
        new Dictionary<string, string>();

    public Foo() { }

    public string Bar { get; private set; }

    public IReadOnlyDictionary<string, string> Corge => 
        (IReadOnlyDictionary<string, string>)_corge;

    // API v3
    public static Foo Build(Action<Builder> build)
    {
        return Builder.Build(build);
    }

    public class Builder
    {
        private Foo _foo = new Foo();

        private Builder() { }

        // API v1
        public static Builder Create()
        {
            return new Builder();
        }

        // API v2
        public static Foo Build(Action<Builder> build)
        {
            var builder = new Builder();
            build(builder);
            return builder.ToFoo();
        }

        public Builder Bar(string bar)
        {
            _foo.Bar = bar;
            return this;
        }

        public Builder AddCorge(string key, string value)
        {
            _foo._corge.Add(key, value);
            return this;
        }

        public Foo ToFoo()
        {
            // prevents from reusing the builder after "ToFoo"
            var result = _foo;
            _foo = null;
            return result;
        }
    }
}

Usage with API v1:

var fooBuilder = Foo.Builder.Create();
fooBuilder.Bar("baz");
var foo = fooBuilder.ToFoo();
fooBuilder.Bar("QUUX"); // bam!

or with API v2:

var foo2 = Foo.Builder.Build(b => b.Bar("baz"));

or even shorther with API v3:

var foo2 = Foo.Build(b => b.Bar("baz"));
\$\endgroup\$

3 Answers 3

7
\$\begingroup\$

When I think of immutable object I think of get only properties backed with readonly fields.

public string Bar { get; private set; }

That's not a fully immutable instance you still have the private set but removing the set completely will cause problems in your implementation, because your nested Builder class relies on the fact that it can set them.

What I think is better in this case is to have a private constructor to initalize your get only properties and instead of saving some private instance of the Foo object in the Builder you can save values :

public class Foo
{
    public string Bar { get; }

    public IDictionary<string, string> Corge { get; }

    private Foo(string bar, IDictionary<string, string> corge)
    {
        Bar = bar;
        Corge = corge;
    }

    public class Builder
    {
        private string _bar;
        private readonly IDictionary<string, string> _corge = new Dictionary<string, string>();

        public Builder Bar(string bar)
        {
            _bar = bar;
            return this;
        }

        public Builder AddCorge(string key, string value)
        {
            _corge.Add(key, value);
            return this;
        }

        public Foo Build()
        {
            return new Foo(_bar, _corge);
        }
    }
}

Usage :

Foo foo = new Foo.Builder().Bar("bar").AddCorge("key", "value").Build();

The action delegate doesn't quite fit in my solution so I just removed it.

You can add the public empty constructor if you want to. I personally wouldn't like to have static methods to create an immutable object, I prefer everything to be instance based.

One builder to rule them all..

One thing that I really don't like about your code is this

var fooBuilder = Foo.Builder.Create();
fooBuilder.Bar("baz");
var foo = fooBuilder.ToFoo();
fooBuilder.Bar("QUUX"); // bam!

You can't use the same builder to create multiple objects and it's not obvious that it will crash unless you check how the Builder class is implemented.

My solution fixes that

var fooBuilder = new Foo.Builder();
fooBuilder.Bar("baz");
var foo1 = fooBuilder.Build();
fooBuilder.Bar("QUUX"); // no bam!
var foo2 = fooBuilder.Build();
\$\endgroup\$
2
\$\begingroup\$

I like the syntax of your builder. Instead of having a contructor with multiple overloads, your builder provides a readable and very flexible solution. For instance

var service = Service.Builder
    .UsingProvider(provider)
    .WithValue("ValueA")
    .ExcludingOptions("OptionB", "OptionC")        
    .Create();

looks much better than something than

var excludeOptions = new string[] {"OptionB", "OptionC"};
var service = new Service(provider, "ValueA", excludeOptions);

However, if the constructor becomes to complex, its probably the time to split the class in multiple classes (if possible) ;). For simple constructors (with 1-2 parameters) the implementation of the builder is too much effort. But for the few use cases that remain it seems to be a good alternative to "monster constructors". :).

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I agree. I usually try to avoid too many parameters in a constructor but this time all my refactoring efforts ended in some FooProperties class that couldn't be reduced any further - it was about a sql server table parameters that a user can set, like data types, column names and lengths, additional columns and their properties that I use to dynamicaly create a select/insert/update queries and there is no way to that I can separate them. They are all needed because they describe a single table and they must not be changed later. That's why I was looking for an efficient initialization ;-) \$\endgroup\$
    – t3chb0t
    Commented Jun 27, 2016 at 11:47
1
\$\begingroup\$

Depending on your needs, using access modifier to corge_ and Bar, you could make Foo mutable within the assembly, but immutable to other assemblies.

public class Foo
{
    public string Bar { get; internal set; }
    internal IDictionary<string, string> _corge = new Dictionary<string, string>();
}

in this way

public class Foobuilder
{
    ...
    public Foobuilder Bar(string bar)
    {
        _foo.Bar = bar;
        return this;
    }
    ...
}

so Foo is immutable to other assemblies.

\$\endgroup\$
1
  • \$\begingroup\$ I used to write code like this before but after givig it a second thought I try to avoid it now ;-) \$\endgroup\$
    – t3chb0t
    Commented Jun 27, 2016 at 11:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.