Search code examples
d

Immutable value objects with nested objects and builders


we have a large system having lots of value objects which are currently documented to be immutable and which do not provide and methods to mutate the objects. While rewriting some parts of the code I thought it would be a good exercise to not only document the classes to be immutable but declare them as immutable.

A Value object with a nested object looks like this:

immutable class Nested
{
    int i;

    public this(int i) immutable
    {
        this.i = i;
    }
}

immutable class Value
{
    private Nested nested_;

    public immutable(Nested) nested() const
    {
        return this.nested_;
    }

    package this(immutable(Nested) nested)
    {
        this.nested_ = nested;
    }
}

Whenever a change to any of the values needs to be done we use a builder to create a copy and modify the attributes. So far so good - but when it comes to nested objects I'm running into trouble. Using the original object to create a copy from the builder can only get the immutable nested object and store it. But how can the builder change the nested object to a new one?

I came up with Rebindable from std.typecons - but I'm not sure whether this is good practice or not.

class Builder
{
    import std.typecons : Rebindable;

    Rebindable!(immutable(Nested)) nested_;

    this()
    {
        this.nested_ = null;
    }

    this(immutable(Value) value)
    {
        this.nested_ = value.nested;
    }

    public void nested(immutable(Nested) nested)
    {
        this.nested_ = nested;
    }

    public immutable(Value) value() const
    {
        return new immutable Value(this.nested_);
    }
}

void main()
{
    import std.stdio : writefln;

    immutable value = new immutable Value(new immutable Nested(1));

    writefln("i = %d", value.nested.i);

    auto builder = new Builder(value);
    immutable newNested = new immutable Nested(2);
    builder.nested = newNested;

    writefln("i = %d", builder.value.nested.i);
}

Am I thinking too much about immutability and const correctness?

Regards,

Rooney


Solution

  • Your solution with Rebindable is OK, and I considere it as a best way to achive this. Another possible solution is to make nested_ mutable and use casts, but this is not so elegant and safe:

    class Builder
    {
        import std.typecons : Rebindable;
        union
        {
            private Nested m_nested_;
            immutable(Nested) nested_;
        }
    
        this()
        {
            this.nested_ = null;
        }
    
        this(immutable(Value) value)
        {
            this.nested_ = value.nested();
        }
    
        public void nested(immutable(Nested) nested)
        {
            this.m_nested_ = cast(Nested)nested;
        }
    
        public immutable(Value) value() const
        {
            return new immutable Value(this.nested_);
        }
    }