22

I have an algorithm which creates a collection of objects. These objects are mutable during creation, since they start out with very little, but then they are populated with data in different places within the algorithm.

After the algorithm is completed, the objects should never be changed - however they are consumed by other parts of the software.

In these scenarios, is it considered good practice to have two versions of the class, as described below?

  • The mutable one is created by the algorithm, then
  • On completion of the algorithm, the data is copied into immutable objects which are returned.
2

3 Answers 3

46

You can perhaps use the builder pattern. It uses a separate 'builder' object with the purpose of collecting the necessary data, and when all data is collected it creates the actual object. The created object can be immutable.

2
  • Your solution was the correct one for my requirements (not all of which were stated). Upon investigation the returned objects don't all have the same characteristics. The builder class has a lot of nullable fields and when the data has been built up, it chooses which one of 3 different classes to generate: these are related to each other by inheritance. Jul 27, 2015 at 15:52
  • 2
    @Paul In that case, if this answer solved your problem, you should mark it as accepted.
    – Riking
    Jul 27, 2015 at 22:37
24

A simple way to achieve this would be to have an interface that allows one to read properties and call only read only methods and a class that implements that interface which also lets you write that class.

Your method that creates it, deals with the former, and then returns the latter providing only a read only interface to interact with. This would require no copying and it allows you to easily fine-tune behaviors you want available to the caller as opposed to the creator.

Take this example:

public interface IPerson 
{
    public String FirstName 
    {
        get;
    }

    public String LastName 
    {
        get;
    }
} 

public class PersonImpl : IPerson 
{
    private String firstName, lastName;

    public String FirstName 
    {
        get { return firstName; }
        set { firstName = value; }
    }

    public String LastName 
    {
        get { return lastName; }
        set { lastName = value; }
    }
}

class Factory 
{
    public IPerson MakePerson() 
    {
        PersonImpl person = new PersonImpl();
        person.FirstName = 'Joe';
        person.LastName = 'Schmoe';
        return person;
    }
}

The only disadvantage to this approach is that one could simply cast it to the implementing class. If it were a matter of security, then simply using this approach is insufficient. A workaround for this is that you can create a facade class to wrap the mutable class, which simply presents an interface that the caller works with and cannot have access to the internal object.

In this way, not even casting will help you. Both can derive from the same read only interface, but casting the returned object will only give you the Facade class, which is immutable as it doesn't change the underlying state of the wrapped mutable class.

It's worth mentioning that this doesn't follow the typical trend in which an immutable object is constructed once and for all through its constructor. Understandably, you might be having to deal with many parameters, but you should ask yourself if all these parameters need to be defined up front or if some can be introduced later. In that case, a simple constructor with only required parameters should be used. In other words, don't use this pattern if it is covering up another problem in your program.

6
  • 1
    Security is not better by returning a "read only" object, because the code which get the object, can still modify the object using reflection. Even a string can be modified(Not copied, modified in-place) using reflection.
    – MTilsted
    Jul 27, 2015 at 15:55
  • The security problem can be neatly solved with a private class Jul 27, 2015 at 18:18
  • @Esben: You still have to contend with MS07-052: Code execution results in code execution. Your code is running in the same security context as their code, so they can just attach a debugger and do whatever they wish.
    – Kevin
    Jul 27, 2015 at 18:19
  • Kevin1 you can say that about all encapsulations. I'm not trying to protect against reflection. Jul 27, 2015 at 19:48
  • 1
    The problem with using the word "security" is that immediately someone assumes that what I say to be the more secure option is equivalent to maximum security and best practice. I never said either. If you're handing the library for someone to use, unless obfuscated (and sometimes even when obfuscated), you can forget about guaranteeing security. However, I think we can all agree on the fact that if you're tampering with a returned facade object using reflection in order to retrieve the internal object contained within that you're not exactly using the library as it should be used.
    – Neil
    Jul 28, 2015 at 6:29
8

You can use the Builder pattern as @JacquesB says, or alternatively think why is it actually that these objects of yours have to be mutable during creation?

In others words, why does the process of creating them have to be spread in time, as opposed to passing all the required values into the constructor and creating instances in one go?

Because the Builder could be a good solution for the wrong problem.

If the issue is that you end up with a constructor that's like 10 parameters long, and you'd like to mitigate it by building the object little by little, it could indicate that the design is messed up, and these 10 values should be "bagged" / grouped into a few objects... or the main object split into a few smaller ones...

In which case - stick to immutability all the way, just improve the design.

1
  • It would be difficult to create it all at once since the code performs multiple database queries to get the data; it compares data in different tables, and in different databases. Jul 27, 2015 at 15:53

Your Answer

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

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