patterncsharpMinor
Initializing immutable objects with a nested builder
Viewed 0 times
objectsinitializingwithbuildernestedimmutable
Problem
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
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:
Usage with API v1:
```
var fooBuilder = Foo.Builder.Create();
fooBuilder.Bar(
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 _corge =
new Dictionary();
public Foo() { }
public string Bar { get; private set; }
public IReadOnlyDictionary Corge =>
(IReadOnlyDictionary)_corge;
// API v3
public static Foo Build(Action 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 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(
Solution
When I think of immutable object I think of get only properties backed with
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
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
Usage :
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
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
My solution fixes that
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 Corge { get; }
private Foo(string bar, IDictionary corge)
{
Bar = bar;
Corge = corge;
}
public class Builder
{
private string _bar;
private readonly IDictionary _corge = new Dictionary();
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();Code Snippets
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);
}
}
}Foo foo = new Foo.Builder().Bar("bar").AddCorge("key", "value").Build();var fooBuilder = Foo.Builder.Create();
fooBuilder.Bar("baz");
var foo = fooBuilder.ToFoo();
fooBuilder.Bar("QUUX"); // bam!var fooBuilder = new Foo.Builder();
fooBuilder.Bar("baz");
var foo1 = fooBuilder.Build();
fooBuilder.Bar("QUUX"); // no bam!
var foo2 = fooBuilder.Build();Context
StackExchange Code Review Q#133066, answer score: 7
Revisions (0)
No revisions yet.