Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

When designing classes to hold your data model I've read it can be useful to create immutable objects but at what point does the burden of constructor parameter lists and deep copies become too much and you have to abandon the immutable restriction?

For example, here is an immutable class to represent a named thing (I'm using C# syntax but the principle applies to all OO languages)

class NamedThing
{
    private string _name;    
    public NamedThing(string name)
    {
        _name = name;
    }    
    public NamedThing(NamedThing other)
    {
         this._name = other._name;
    }
    public string Name
    {
        get { return _name; }
    }
}

Named things can be constructed, queried and copied to new named things but the name cannot be changed.

This is all good but what happens when I want to add another attribute? I have to add a parameter to the constructor and update the copy constructor; which isn't too much work but the problems start, as far as I can see, when I want to make a complex object immutable.

If the class contains may attributes and collections, containing other complex classes, it seems to me the constructor parameter list would become a nightmare.

So at what point does a class become too complex to be immutable?

share|improve this question
    
I always make an effort to make the classes in my model immutable. If you're having huge, long contructor parameter lists then maybe your class is too big and it can be split down? If your lower-level objects are also immutable and follow the same pattern then your higher-level objects shouldn't suffer (too much). I find it MUCH harder to change an existing class to become immutable than to make a data model immutable when I'm starting from scratch. –  rmx Apr 14 '11 at 10:19
    
You could look at the Builder pattern suggested in this question: stackoverflow.com/questions/1304154/… –  Ant Apr 14 '11 at 10:22
    
Have you looked at MemberwiseClone? You don't have to update the copy constructor for each new member. –  kevin cline Apr 14 '11 at 15:15
2  
@Tony If your collections and everything they contain are also immutable, you don't need a deep copy, a shallow copy is sufficient. –  mjcopple Apr 14 '11 at 16:24
2  
As an aside, I commonly use "set-once" fields in classes where the class needs to be "fairly" immutable, but not completely. I find this solves the problem of huge constructors, but provides most of the benefits of immutable classes. (namely, your internal class code not having to worry about a value changing) –  Earlz Apr 14 '11 at 23:04
show 1 more comment

7 Answers

up vote 15 down vote accepted

When they become a burden? Very quickly (specially if your language of choice does not provide sufficient syntactic support for immutability.)

Immutability is being sold as the silver bullet for the multi-core dilemma and all that. But immutability in most OO languages forces you to add artificial artifacts and practices in your model and process. For each complex immutable class you must have an equally complex (at least internally) builder. No matter how you design it, it stills introduces strong coupling (thus we better have a good reason to introduce them.)

It is not necessarily possible to model everything in small non-complex classes. So for large classes and structures, we artificially partition them - not because that makes sense in our domain model, but because we have to deal with their complex instantiation and builders in code.

It is worse still when people take the idea of immutability too far in a general purpose language like Java or C#, making everything immutable. Then, as a result, you see people forcing s-expression constructs in languages that do not support such things with ease.

Engineering is the act of modeling through compromises and trade-offs. Making everything immutable by edict because someone read that everything is immutable in X or Y functional language (a completely different programming model), that is not acceptable. That is not good engineering.

Small, possibly unitary things can be made immutable. More complex things can be made immutable when it makes sense. But immutability is not a silver bullet. The ability to reduce bugs, to increase scalability and performance, those are not the sole function of immutability. It is a function of proper engineering practices. After all, people have written good, scalable software without immutability.

Immutability gets to become a burden really fast (it adds to accidental complexity) if it is done without a reason, when it is done outside of what it make sense in the context of a domain model.

I, for one, try to avoid it (unless I'm working in a programming language with good syntactic support for it.)

share|improve this answer
4  
luis, have you noticed how the well-written, pragmatically correct answers written with an explanation of simple yet sound engineering principles tend not to get as many votes as those using state-of-the-art coding fads? This is a great, great answer. –  Huperniketes Jul 1 '11 at 10:29
3  
Thanks :) I've noticed the rep-trends myself, but that's ok. Fad fanboys churn code that we later get to repair at better hourly rates, hahah :) jk... –  luis.espinal Jul 1 '11 at 14:05
1  
Silver bullet? no. Worth the bit of awkwardness in C# / Java (it's not really that bad)? Absolutely. Also, multicore's role in immutability is quite minor... the real benefit is ease of reasoning. –  Mauricio Scheffer Feb 13 '12 at 15:00
    
@Mauricio - if you say so (that immutability in Java isn't that bad). Having worked on Java from 1998 till 2011, I'd beg to differ, it is not trivial exempt in simple code bases. However, people have different experiences, and I acknowledge that my POV is not free of subjectivity. So sorry, can't agree there. I do agree, however, with the ease of reasoning being the most important thing when it comes to immutability. –  luis.espinal Mar 5 '12 at 15:51
add comment

I went through a phase of insisting on classes being immutable where possible. Had builders for pretty much everything, immutable arrays, etc, etc. I found the answer to your question is simple: At what point do immutable classes become a burden? Very quickly. As soon as you want to serialize something, you have to be able to deserialize, which means it must be mutable; as soon as you want to use an ORM, most of them insist on properties being mutable. And so on.

I eventually replaced that policy with immutable interfaces to mutable objects.

class NamedThing : INamedThing
{
    private string _name;    
    public NamedThing(string name)
    {
        _name = name;
    }    

    public NamedThing(NamedThing other)
    {
        this._name = other._name;
    }

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

interface INamedThing
{
    string Name { get; }
}

Now the object has flexibility but you can still tell calling code that it shouldn't edit these properties.

share|improve this answer
    
I'm want to make use of serialization so it would seem I'm already off to a difficult start. –  Tony Apr 14 '11 at 16:01
    
I think you wanted NamedThing to implement INamedObject. –  Wesley Wiser Apr 14 '11 at 20:40
    
@wawa - Well spotted. Corrected –  pdr Apr 14 '11 at 21:43
3  
Minor quibbles aside, I agree that immutable objects can become a pain in the butt very quickly when programming in an imperative language, but I'm not really sure if an immutable interface really solves the same problem, or any problem at all. The prime reason to use an immutable object is so you can throw it around anywhere at any time and never have to worry about somebody else corrupting your state. If the underlying object is mutable then you don't have that guarantee, especially if the reason you kept it mutable was because various things need to mutate it. –  Aaronaught Apr 14 '11 at 22:05
1  
@supercat: Incidentally, Microsoft agrees with me. They released an immutable collections package and notice that they're all concrete types, because you cannot ever guarantee that an abstract class or interface is truly immutable. –  Aaronaught Feb 23 at 18:17
show 23 more comments

You have a couple of things at work here. Immutable data sets are great for multithreaded scalability. Essentially, you can optimize your memory quite a bit so that one set of parameters is one instance of the class--everywhere. Because the objects never change you don't have to worry about synchronizing around accessing its members. That's a good thing. However, as you point out, the more complex the object is the more you need some mutability. I would start with reasoning along these lines:

  • Is there any business reason why an object can change its state? For example, a user object stored in a database is unique based on its ID, but it has to be able to change state over time. On the other hand when you change coordinates on a grid, it ceases to be the original coordinate and so it makes sense to make coordinates immutable. Same with strings.
  • Can some of the attributes be computed? In short, if the other values in the new copy of an object are a function of some core value you pass in, you can either compute them in the constructor or on demand. This reduces the amount of maintenance as you can initialize those values the same way on copy or create.
  • How many values make up the new immutable object? At some point the complexity of creating an object becomes non-trivial and at that point having more instances of the object can become a problem. Examples include immutable tree structures, objects with more than three passed in parameters, etc. The more parameters the more possibility of messing up the order of parameters or nulling out the wrong one.

In languages that only support immutable objects (such as Erlang), if there is any operation that seems to modify the state of an immutable object, the end result is a new copy of the object with the updated value. For example, when you add an item to a vector/list:

myList = lists:append([[1,2,3], [4,5,6]])
% myList is now [1,2,3,4,5,6]

That can be a sane way of working with more complicated objects. As you add a tree node for example, the result is a new tree with the added node. The method in the above example returns a new list. In the example in this paragraph the tree.add(newNode) would return a new tree with the added node. For the users, it becomes easy to work with. For the library writers it becomes tedious when the language doesn't support implicit copying. That threshold is up to your own patience. For the users of your library, the most sane limit I've found is about three to four parameters tops.

share|improve this answer
    
If one would be inclined to use a mutable object reference as a value [meaning that no references are contained within its owner and never exposed], constructing a new immutable object which holds the desired "changed" contents is equivalent to modifying the object directly, though is likely slower. Mutable objects, however, can also be used as entities. How would one make things that behave like entities without mutable objects? –  supercat Feb 23 at 17:24
add comment

You can avoid the copy problem if you store all of your immutable fields in an inner struct. This is basically a variation of the memento pattern. Then when you want to make a copy, just copy the memento:

class MyClass
{
    struct Memento
    {
        public int field1;
        public string field2;
    }

    private readonly Memento memento;

    public MyClass(int field1, string field2)
    {
        this.memento = new Memento()
            {
                field1 = field1,
                field2 = field2
            };
    }

    private MyClass(Memento memento) // for copying
    {
        this.memento = memento;
    }

    public int Field1 { get { return this.memento.field1; } }
    public string Field2 { get { return this.memento.field2; } }

    public MyClass WithNewField1(int newField1)
    {
        Memento newMemento = this.memento;
        newMemento.field1 = newField1;
        return new MyClass(newMemento);
    }
}
share|improve this answer
    
I think the inner struct is not necessary. It's just another way of doing MemberwiseClone. –  Codism Apr 14 '11 at 22:17
    
@Codism - yes and no. There are times when you might need other members that you don't want to clone. What if you were using lazy evaluation in one of your getters and caching the result in a member? If you do a MemberwiseClone, you'll clone the cached value, then you'll change one of your members that the cached value depends on. It's cleaner to separate the state from the cache. –  Scott Whitlock Apr 15 '11 at 13:44
    
It might be worth mentioning another advantage of the inner struct: it makes it easy for an object to copy its state to an object to which other references exist. A common source of ambiguity in OOP is whether a method which returns an object reference is returning a view of an object that might change outside the recipient's control. If instead of returning an object reference, a method accepts an object reference from the caller and copies state to it, ownership of the object will be much clearer. Such an approach doesn't work well with freely-inheritable types, but... –  supercat Feb 23 at 17:29
    
...it can be very useful with mutable data holders. The approach also makes it very easy to have "parallel" mutable and immutable classes (derived from an abstract "readable" base) and have their constructors be able to copy data from each other. –  supercat Feb 23 at 17:32
add comment

If you have multiple final class members and don't want them to be exposed to all objects who need to create it, you can use the builder pattern:

class NamedThing
{
    private string _name;    
    private string _value;
    private NamedThing(string name, string value)
    {
        _name = name;
        _value = value;
    }    
    public NamedThing(NamedThing other)
    {
        this._name = other._name;
        this._value = other._value;
    }
    public string Name
    {
        get { return _name; }
    }

    public static class Builder {
        string _name;
        string _value;

        public void setValue(string value) {
            _value = value;
        }
        public void setName(string name) {
            _name = name;
        }
        public NamedThing newObject() {
            return new NamedThing(_name, _value);
        }
    }
}

the advantage is that you can easily create a new object with only a different value of a differtent name.

share|improve this answer
add comment

I don't think there is a general answer to this. The more complex a class is, the harder it is to reason about its state changes, and the costlier it is to create new copies of it. So above some (personal) level of complexity it will become too painful to make/keep a class immutable.

Note that a too complex class, or a long method parameter list are design smells per se, regardless of immutability.

So usually the preferred solution would be to break such a class into multiple distinct classes, each of which can be made mutable or immutable on its own. If this is not feasible, it can be turned mutable.

share|improve this answer
add comment

Any time you're designing something that has a growing complexity you have to continually ask yourself if you truly NEED that complexity. In the case you mentioned, as you grow more and more properties into it, you should ask yourself if this class truly needs to be immutable or if it truly should be a standard class. You should consider the level of copying you are looking for and determine what truly should be copied or even if you need a deep copy. Also, depending on the language being used, there may be tools to help you deal with this complexity (such as reflection) at a computational cost rather than a design cost.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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