Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent modification of mutable object

Tags:

java

Let's suppose I have a simple class:

class Some {
  private Something something;

  public Some() {}

  public Something getSomething() {
    return this.something;
  }

  public void setSomething(Something something) {
    this.something = something;
  }
}

and I have a method:

void change(Some some) {
  //change state of object via
  some.setSomething(new Something());
}

How can I prevent the state of the Some object from changing inside the change() method?

Condition: you can't modify the Some class (i.e. remove the setSomething() method, make the something field as final, declare the class as final)

Solution I've found:

interface ISome {
  void setSomething(Something something);
  Something getSomething();
}

class Some implements ISome {
  private Something something;

  public Some() {}

  public Something getSomething() {
    return this.something;
  }

  public void setSomething(Something something) {
    this.something = something;
  }
}

class SomeProxy implements ISome {
  private ISome some;
  
  public SomeProxy(ISome some) {
    this.some = some;
  }

  public Something getSomething() {
    return some.getSomething();
  }

  public void setSomething(Something something) {
    throw new IllegalOperationException("You cant modify an object!");
  }
}

Method:

void change(ISome some) {
  some.setSomething(new Something());
}

And then if we call the change() method like this:

change(new SomeProxy());

we will get an exception.

Is where in java mechanism that could help with that problem and prevent the object from being modified inside the change() method without creating a proxy?

like image 474
donquih0te Avatar asked Sep 26 '20 07:09

donquih0te


People also ask

How can we prevent immutable class?

How to preserve Immutability? There are two ways to avoid this problem, first, don't provide getters to mutable objects if you can avoid it. If you must, then consider returning a copy or clone of the mutable object. If you are returning a collection, you could wrap it as an unmodifiable collection.

How do you make an inner object immutable in Java?

For an object to be immutable, all of its properties must be immutable. Its state must not be changeable. To do that, you have to put an immutable facade on NormalObject , you can't directly return a NormalObject .


1 Answers

An interface without setter

I agree with the comment of @kaya3, just define an interface without the setter and give that to your function:

interface ISome {
  Something getSomething();
}

class Some implements ISome {
  private Something something;

  public Some() {}

  public Something getSomething() {
    return this.something;
  }

  public void setSomething(Something something) {
    this.something = something;
  }
}


void change(ISome some) {
  //Doesn't compile there no method setSomething in the interface
  some.setSomething(new Something());
}

Please notice that you still modify the Some class, you make it implement an interface and if that class is from a lib and you really can't touch it, it doesn't work. I used it because you did it yourself, but still this isn't a great solution if you really can't modify the class.

Still this is great solution to segregate interface and implementation. The interface is part of your public API and nobody can modify the object with the public interface. So the API always return instances of the interface but use the concrete implementation inside.

A proxy that really don't touch the original class

You can go a step further by not making Some implementing the interface and introduce back your proxy if the class if from an external API:

class Some {
  private Something something;

  public Some() {}

  public Something getSomething() {
    return this.something;
  }

  public void setSomething(Something something) {
    this.something = something;
  }
}

class SomeProxy implements ISome {
  private Some some;
  
  public SomeProxy(Some some) {
    this.some = some;
  }

  public Something getSomething() {
    return some.getSomething();
  }
}


void change(ISome some) {
  //Doesn't compile there no method setSomething in the interface
  some.setSomething(new Something());
}

This way you never changed the Some class one bit but to work properly you must ensure the the Some class isn't visible from the client code. You can achieve that thanks to jave 9 modules, thanks to OSGI or thanks to runtime dependency in maven.

The proxy is just a specific case of a more generic and common one

If you think about it, it is specific case of just having a class with a private field for its own use, and publishing part of that object behavior but not all. This class doesn't need to do only that. She may have a purpose that isn't being a proxy but just happen to have a Some instance to do its job. She control the Some instance, can call the setter because she is in control, but for the outside world, she only give access to the setter.

This is the general case. That class does implement a concept/feature and encapsulate properly its internal state and implementation. And if you pass that class to any function, nobody can modify the interal state in an unwanted way:

class OtherBehavior {
  private Some some;
  private OtherThing thing;
  [...]

  public Something getSomething() {
    return some.getSomething();
  }

  public void doStuff() {
    [...]
  }
  [...]
}

void change(OtherBehavior other) {
  // Doesn't compile:
  other.setSomething(new Something()); 
  // Doesn't compile:
  other.some.setSomething(new Something());
  // Ok:
  other.getSomething();
}

Please prefer immutable design in the general case

It is usually recognized that an immutable design is overall better in many cases. So allows the constructor to get the Something() instance, and then make the field final and provide no setter. You'll find that in many case you don't need the setter at all and can just create the immutable object when the Something instance is already available. It is much simpler and robust design. If you API need to "change" to a new Something, you could always also create a new Some instance:

class Some {
  private final Something something;

  public Some(Something something) {
    this.something = something;
  }

  public Something getSomething() {
    return this.something;
  }
}

This has benefits:

  • you can add the equals/hashCode and make it participate to maps/set collections safely, you can even compute the hash at construction time and cache it for improved perf (helped us a lot on perf sensitive code)
  • you can share the object arround threads safely
  • you prevent bug occuring by a client calling that setter, so you can pass it anywhere without any risk of bug.
  • you don't need a complex proxy at all, making a copy or deal with exceptions.

But of course this imply you are in control of the Some class.

like image 165
Nicolas Bousquet Avatar answered Oct 21 '22 14:10

Nicolas Bousquet