Skip to content
Advertisement

Changing an immutable class in legacy code

I want to replace the (mutable) class

class OldValue {
  private int value;
  
  public OldValue(int value) { this.value = value; }
  
  public int getValue() { return value; }
  
  public void setValue(int value) { this.value = value; }
}

with the immutable class

class NewValue {
  private final int value;
  
  public NewValue(int value) { this.value = value; }
  
  public int getValue() { return value; }
  
  /* no setter, obviously */
}

In most parts of the code something like newValue.set(777) can be replaced by newValue = new NewValue(777). However, there are some legacy parts of the code with something like

class ManagementServiceProviderFacadeSingletonAbstractFactoryObserver {

  public void setTheValue(OldValue oldValue) {
    oldValue.setValue(555);
  }

}

class Data {
  private OldValue oldValue;
  
  public OldValue get() { return oldValue; }
}


class SomewhereElse {

  public void frobnicate(Data data, ManagementServiceProviderFacadeSingletonAbstractFactoryObserver x) {
    x.setTheValue(data.get());
  }
}

These legacy parts are very hard to change and we would like to adapt it as little as possible. Is there a way to write some kind of “evil” setter method that could be used in the legacy code? Something like

class EvilSetter {

  public static void evilSet(NewValue newValue, int x) {
    // temporarily make newValue.value both public and non-final, set it to x
  }
}

Is there some other way without losing the immutability and elegance of the new design?

Advertisement

Answer

I’d probably keep both types, leave the legacy code using the old type alone and provide adapter methods for converting between the two.

public class LegacyValueAdapter {

    public static OldValue toOldValue(NewValue newValue) {
        return new OldValue(newValue.getValue);
    }

    public static NewValue toNewValue (OldValue oldValue) {
        return new NewValue(oldValue.getValue);
    }

}

Clearly document that these should only be used for interfacing with the legacy code, mark as deprecated as needed. This clearly isn’t ideal, but that applies to the entire situation.

If the class is more complicated (e.g. also has methods with some non-trivial behavior), it might be worth implementing OldValue by wrapping an instance of NewValue, but in this simple case that won’t provide much benefit.


I strongly advise against any reflection-hacks, as they would invalidate most the benefits of using the immutable types in the first place. Don’t give me a type that looks immutable, but then sneakily change the value somehow – that breaks expectations of users and hides the risks for race-conditions.

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement