21

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?

Tony
  • 349
  • 2
  • 12
  • 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. – Nobody Apr 14 '11 at 10:19
  • 1
    You could look at the Builder pattern suggested in this question: http://stackoverflow.com/questions/1304154/immutable-objects-with-object-initialisers – 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
  • @kevin: I'd not looked at it before but I'm going to have classes with collections and it looks like MemberwiseClone only performs a shallow copy. – Tony Apr 14 '11 at 15:21
  • 3
    @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
  • @Earlz how do you do this, more specifically? by throwing runtime exceptions if someone tries to set them twice, or are you somehow capturing it in the type-system? – sara Jul 01 '16 at 22:23
  • I know this is an old question, but why would you even *have* a copy constructor? If the object is immutable and has value identity (or identity doesn't matter), why would you ever create a copy of it? – Sebastian Redl Dec 14 '17 at 10:26
  • It may be an old question but the problem is still with us :) As to needing a copy constructor there are other questions on SO regarding this. Such as https://stackoverflow.com/questions/41692451/do-immutable-types-need-copy-constructors – Tony Dec 23 '17 at 03:25

7 Answers7

25

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.)

luis.espinal
  • 2,560
  • 1
  • 20
  • 17
  • 6
    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 01 '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 01 '11 at 14:05
  • 5
    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 05 '12 at 15:51
14

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.

pdr
  • 53,387
  • 14
  • 137
  • 224
  • 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
  • Not that it matters for pseudocode but the class still doesn't match the interface - one says `INamedThing` and the other says `INamedObject`. – Aaronaught Apr 14 '11 at 22:01
  • 10
    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
  • @Aaronaught - again, well spotted. *sighs at self* – pdr Apr 14 '11 at 22:06
  • 1
    @Aaronaught - Interesting point. I guess it's a psychological thing more than an actual protection. However, your last line has a false premise. The reason for keeping it mutable is more that various things need to instantiate it and populate via reflection, not to mutate once instantiated. – pdr Apr 14 '11 at 22:11
  • @pdr: Thank for your answer and examples. I accepted luis.espinal answer as I thought it was more applicable to what I was trying to resolve, although it was a hard decision to choose his answer. Your has more upvotes (including one from me) and I wish I could choose more than one answer to be the accepted one. – Tony Apr 19 '11 at 10:44
  • @Tony - Don't worry, I'm not one who is here to grab every point. And Luis's answer is a good one. Says much the same as mine but more of the why and less of the alternatives. It's all good. – pdr Apr 20 '11 at 09:53
  • "As soon as you want to serialize something, you have to be able to deserialize, which means it must be mutable" -> plain wrong. Also, if the ORM requires mutability, it's the ORM's problem. – Mauricio Scheffer Feb 13 '12 at 15:01
  • Your interface `INamedThing` looks like a read-only interface, rather than an immutable interface, since your model implementation is in fact mutable. An immutable interface will guarantee to its callers that reading any particular property on any particular instance of non-broken implementation will always yield the same value. – supercat Jul 06 '12 at 20:45
  • @Aaronaught: I'm irked at the way "immutable" and "read-only" seem to be viewed as synonymous; indeed, "read-only" is often regarded as synonymous with "readable". I wonder why framework designers don't make such distinctions even in such simple things as the names of interfaces like `IReadOnlyList` [which should IMHO be `IReadableList`, since it makes no promise that the underlying reference *cannot* be used to modify the list]. Mutability is a burden in some cases, and immutability in others; the burdens could be minimized if things which need to read something immediately, but... – supercat Feb 22 '14 at 21:50
  • ...won't need to change it and won't need to read it in future could be given something of a "readable" interface type and use it directly, and if things that need to be given something to read now and *might* want to read it in future (after they return) could determine whether they need to make a copy of the item since it might change in future. Some simple interfaces could help enormously; IMHO, they should have appeared *ages* ago, but there's no sign of them. – supercat Feb 22 '14 at 21:52
  • @supercat: I really have no idea what you're going on about, sorry. A read-only list is a read-only list, which, yes, is also an immutable data type. It does not provide any operations to modify the collection. So what if there's a mutable underlying concrete type? By your logic, every single type longer than 1 word (including 64-bit integers on a 32-bit architecture) can't be read-only, because you could perform an unsafe cast to a pointer (or some other mutable type) and change its value. A theoretically true characteristic of virtually no practical interest or utility. – Aaronaught Feb 23 '14 at 03:31
  • @Aaronaught: The problem is that there's no way of distinguishing a read-only view of a list which will never change, from a read-only view of a list which could change at some unspecified future time. Consider, for example, a method which is supposed to read all the items from a list and do something with them. Code which has a private list could pass a read-only wrapper to that list, or it could construct a new list detached from the original. Now suppose one wants to make the "process everything" method so it can do its work in parallel with the thread that called it. – supercat Feb 23 '14 at 16:11
  • @Aaronaught: If that routine was passed a read-only view of a mutable list, it will need to make a detached copy before it returns (and then operate on the detached copy). If it received an immutable copy, however, making its own copy would be redundant. Further, if a list contains read-only views of changeable things, those things will need to be copied, but if it contains references to immutable things, it will be sufficient to copy the references. – supercat Feb 23 '14 at 16:17
  • @supercat: What you're saying is a fair point, but essentially it boils down to wanting a guarantee that a type *and all possible derived or assignable types* are immutable, which is actually fundamentally incompatible with the principles of OOP. You can't have "strict" immutability and abstraction at the same time. – Aaronaught Feb 23 '14 at 16:21
  • @Aaronaught: Why not? If there were an interface `IImmutableList` which derived from `IReadOnlyList`, which in turn derived from `IReadableList`, then `List` could implement `ReadableList`, `ReadOnlyCollection` could implement `IReadOnlyList`, and a type `ImmutableList` could derive from `ReadOnlyCollection`, but have a constructor which would memberwise-clone the supplied list if it didn't already implement `IImmutableList`. Deep immutability (or lack thereof) would be implied by the choice of T. – supercat Feb 23 '14 at 17:08
  • @Aaronaught: If one wants to be "really sure" the collection is immutable, one could demand an `ImmutableList`. If one is willing to trust interface implementations to abide by their contract, one could accept an `IImmutableList`. Even if such a design couldn't eliminate 100% of defensive copying, it could probably eliminate the vast majority, in some cases allowing 99% savings on storage and time (if one has many nested collections whose contents are mostly identical, the common parts could be shared). – supercat Feb 23 '14 at 17:11
  • @supercat: How could you prevent someone from implementing `IIMutableList` with a mutable concrete class? Only concrete types can be guaranteed to be immutable; interfaces necessarily define a *subset* of the behaviour of some type, not its entire behaviour. Even if it were theoretically possible for some new OO language to enforce immutability, it would certainly be impossible to bolt on to an existing one. And I don't know why you're still clinging to this distinction of "read-only" vs. "readable" when I've already pointed out its uselessness (*everything* is readable). – Aaronaught Feb 23 '14 at 17:25
  • 1
    @Aaronaught: The same way `IComparable` guarantees that if `X.CompareTo(Y)>0` and `Y.CompareTo(Z)>0`, then `X.CompareTo(Z)>0`. *Interfaces have contracts*. If the contract for `IImmutableList` specifies that the values of all items and properties must be "set in stone" before any instance is exposed to the outside world, then all *legitimate* implementations will do so. Nothing prevents an `IComparable` implementation from violating transitivity, but implementations which do so are illegitimate. If a `SortedDictionary` malfunctions when given an illegitimate `IComparable`,... – supercat Feb 23 '14 at 17:41
  • ...the bug would not be in the `SortedDictionary` class, but in the `IComparable` implementation. Further, even if one wanted to be paranoid, one could use a system-defined `ReallyImmutableList` whose constructor would copy anything it received other than a `ReallyImmutableList`. Having such a thing as a system type would allow code by different authors to pass around references to of such a type without having to make defensive copies, whereas if each author defined their own `ReallyImmutableList` type, that wouldn't work. – supercat Feb 23 '14 at 17:41
  • @supercat: If you trust implementations to follow the interface contracts then you should trust `IReadOnlyList` to actually be read-only, which practically speaking is the same as immutability. You're not being very consistent here. You seem to think that the existing interfaces are insufficient because some implementations might break them, and yet somehow you think your made-up interface names (which communicate exactly the same concept while adding considerable ambiguity) would fare differently. A single read-only interface is fine; you either trust the contracts or you don't. – Aaronaught Feb 23 '14 at 17:49
  • 1
    @Aaronaught: Why should I trust that implementations of `IReadOnlyList` will be immutable, given that (1) no such requirement is stated in the interface documentation, and (2) the most common implementation, `List`, *isn't even read-only*? I'm not quite clear what's ambiguous about my terms: a collection is readable if the data within can be read. It's read-only if it can promise that the data contained cannot be changed unless some *external* reference is held by code which would change it. It's immutable if it can guarantee that it cannot be changed, period. – supercat Feb 23 '14 at 18:05
  • @Aaronaught: Code which receives a readable object and is "trusted" not to change it, but wants to let outside world copy values from it, must encapsulate it in a read-only wrapper first *unless it's already read-only*. Code which wants to persist the contents thereof must make a copy *unless it's already immutable*. Code could wrap objects in read-only wrappers or copy them without regard for whether it was really needed, but that could result in layer upon layer of redundant wrapping, and thousands or millions of redundant copies. – supercat Feb 23 '14 at 18:10
  • @supercat: These are *your* (made-up) definitions, not mainstream ones. Read-only means read-only; it cannot be written to, only read from. The term "read-only" goes back to the very beginnings of computer science where it was used in ROM (Read-Only Memory) or maybe even earlier, which means *can't be written to*, period. It's unfortunate that in the .NET Framework, there is a "read-only" interface which actually means, *"you can't write to it"* instead of *"nobody can write to it"*, but inventing another nearly-identical "no really, it's immutable, I swear" interface only adds ambiguity. – Aaronaught Feb 23 '14 at 18:11
  • This answer is nearly three-years old and this argument isn't really offering a lot. Take it to chat if you need to. – pdr Feb 23 '14 at 18:17
  • 1
    @supercat: Incidentally, Microsoft agrees with me. They released an [immutable collections package](http://blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx) 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 '14 at 18:17
8

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.

Péter Török
  • 46,427
  • 16
  • 160
  • 185
5

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);
    }
}
Scott Whitlock
  • 21,874
  • 5
  • 60
  • 88
  • 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 '14 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 '14 at 17:32
3

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.

Berin Loritsch
  • 45,784
  • 7
  • 87
  • 160
  • 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 '14 at 17:24
0

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

In my opinion it's not worth bothering to make small classes immutable in languages like the one you are showing. I'm using small here and not complex, because even if you add ten fields to that class and it does really fancy operations on them, I doubt it's going to take kilobytes let alone megabytes let alone gigabytes, so any function using instances of your class can simply make a cheap copy of the whole object to avoid modifying the original if it wants to avoid causing external side effects.

Persistent Data Structures

Where I find personal use for immutability is for big, central data structures that aggregate a bunch of teeny data like instances of the class you're showing, like one that stores a million NamedThings. By belonging to a persistent data structure which is immutable and being behind an interface that only allows read-only access, the elements that belong to the container become immutable without the element class (NamedThing) having to deal with it.

Cheap Copies

The persistent data structure allows regions of it to be transformed and made unique, avoiding modifications to the original without having to copy the data structure in its entirety. That's the real beauty of it. If you wanted to naively write functions that avoid side effects that input a data structure that takes gigabytes of memory and only modifies a megabyte's worth of memory, then you'd have to copy the entire freaking thing to avoid touching the input and return a new output. It's either copy gigabytes to avoid side effects or cause side effects in that scenario, making you have to choose between two unpleasant choices.

With a persistent data structure, it allows you to write such a function and avoid making a copy of the entire data structure, only requiring about a megabyte of extra memory for the output if your function only transformed a megabyte's worth of memory.

Burden

As for the burden, there's an immediate one at least in my case. I need those builders people are talking about or "transients" as I call them to be able to effectively express transformations to that massive data structure without touching it. Code like this:

void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
     // Transform stuff in the range, [first, last).
     for (; first != last; ++first)
          transform(stuff[first]);
}

... then has to be written like this:

ImmList<Stuff> transform_stuff(ImmList<Stuff> stuff, int first, int last)
{
     // Grab a "transient" (builder) list we can modify:
     TransientList<Stuff> transient(stuff);

     // Transform stuff in the range, [first, last)
     // for the transient list.
     for (; first != last; ++first)
          transform(transient[first]);

     // Commit the modifications to get and return a new
     // immutable list.
     return stuff.commit(transient);
}

But in exchange for those two extra lines of code, the function is now safe to call across threads with the same original list, it causes no side effects, etc. It also makes it really easy to make this operation an undoable user action since the undo can just store a cheap shallow copy of the old list.

Exception-Safety or Error Recovery

Not everyone might benefit as much as I did from persistent data structures in contexts like these (I found so much use for them in undo systems and non-destructive editing which are central concepts in my VFX domain), but one thing applicable to just about everyone to consider is exception-safety or error recovery.

If you want to make the original mutating function exception-safe, then it needs rollback logic, for which the simplest implementation requires copying the entire list:

void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
    // Make a copy of the whole massive gigabyte-sized list 
    // in case we encounter an exception and need to rollback
    // changes.
    MutList<Stuff> old_stuff = stuff;

    try
    {
         // Transform stuff in the range, [first, last).
         for (; first != last; ++first)
             transform(stuff[first]);
    }
    catch (...)
    {
         // If the operation failed and ran into an exception,
         // swap the original list with the one we modified
         // to "undo" our changes.
         stuff.swap(old_stuff);
         throw;
    }
}

At this point the exception-safe mutable version is even more computationally expensive and arguably even harder to write correctly than the immutable version using a "builder". And a lot of C++ developers just neglect exception-safety and maybe that's fine for their domain, but in my case I like to make sure my code functions correctly even in the event of an exception (even writing tests that deliberately throw exceptions to test exception safety), and that makes it so I have to be able to rollback any side effects a function causes halfway into the function if anything throws.

When you want to be exception-safe and recover from errors gracefully without your application crashing and burning, then you have to revert/undo any side effects a function can cause in the event of an error/exception. And there the builder can actually save more programmer time than it costs along with computational time because: ...

You don't have to worry about rolling back side effects in a function which doesn't cause any!

So back to the fundamental question:

At what point do immutable classes become a burden?

They're always a burden in languages that revolve more around mutability than immutability, which is why I think you should use them where the benefits significantly outweigh the costs. But at a broad enough level for big enough data structures, I do believe there are many cases where it's a worthy trade-off.

Also in mine, I only have a few immutable data types, and they're all huge data structures intended to store massive numbers of elements (pixels of an image/texture, entities and components of an ECS, and vertices/edges/polygons of a mesh).

0

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.

Salandur
  • 111
  • 4
  • I think that your builder being static is not correct. Another thread could change the static name or static value after you set those, but before you call `newObject`. – ErikE Jul 24 '15 at 22:15
  • Only the builder class is static, but its members are not. This means that for every builder you create, they have their own set of members with corresponding values. The class needs to be static so it can be used and instantiated outside of the containing class (```NamedThing``` in this case) – Salandur Jul 28 '15 at 18:56
  • I see what you're saying, I just envision a problem with this because it doesn't lead a developer to "fall into the pit of success." The fact that it uses static variables means that if a `Builder` is reused, there is real risk of the thing happening that I mentioned. Someone might be building a lot of objects and decide that since most of the properties are the same, to simply reuse the `Builder`, and in fact, let's make it a global singleton that is dependency-injected! Whoops. Major bugs introduced. So I think this pattern of mixed instantiated vs. static is bad. – ErikE Jul 29 '15 at 15:54
  • 1
    @Salandur Inner classes in C# are always "static" in the Java inner class sense. – Sebastian Redl Dec 14 '17 at 10:29