Search code examples
javaclass-designimmutabilitymutable

Design of immutable and mutable objects in Java


My problem concerns an API design.

Let's say I'm designing a vector (math/physics meaning). I would like to have both an immutable implemenation and a mutable one.

I have then my vector that looks like this:

public interface Vector {
  public float getX(); public float getY();
  public X add(Vector v);
  public X subtract(Vector v);
  public X multiply(Vector v);
  public float length();
}

I wonder how I can ensure to have both a mutable and an immutable implementation. I don't really like java.util.List's approach (allowing mutability by default) and the UnsupportedOperationException() that Guava's immutable implementation has.

How can I design a "perfect" interface or abstract class Vector with both these implementations?

I've thought about something like this:

public interface Vector {
  ...
  public Vector add(Vector v);
  ...
}
public final class ImmutableVector implements Vector {
  ...
  public ImmutableVector add(Vector v) {
    return new ImmutableVector(this.x+v.getX(), this.y+v.getY());
  }
  ...
}
public class MutableVector implements Vector {
  ...
  public MutableVector add(Vector v) {
    this.x += v.getX();
    this.y += v.getY();
    return this;
  }
  ...
}

So all in all, I would like to check if this approach has flagrant design flaws, which are they and what should I do tho fix these?


Notes: the "vector" stuff is an example of a more general use case. For the sake of my question I could have chosen to rewrite the List interface or anything else. Please focus on the more general use case.


Final choice, after answers below, based on Joda-time as someone explained but now edited:

/** Basic class, allowing read-only access. */
public abstract class ReadableVector {
  public abstract float getX(); public abstract float getY();
  public final float length() {
    return Vectors.length(this);
  }
  // equals(Object), toString(), hashCode(), toImmutableVectors(), mutableCopy()
}
/** ImmutableVector, not modifiable implementation */
public final class ImmutableVector extends ReadableVector implements Serializable {
  // getters
  // guava-like builder methods (copyOf, of, etc.)
}
/** Mutable implementation */
public class Vector extends ReadableVector implements Serializable {
  // fields, getters and setters
  public void add (ReadableVector v) {/* delegate to Vectors */}
  public void subtract(ReadableVector v) {/* delegate to Vectors */}
  public void multiply(ReadableVector v) {/* delegate to Vectors */}
}
/** Tool class containing all the logic */
public final class Vectors {
  public static ImmutableVector add(ReadableVector v1, ReadableVector v2) {...}
  public static void addTo(Vector v1, ReadableVector v2) {...}
  ...
}

I changed Vector from an interface to a abstract class because basically a vector shouldn't be anything else.

Thank you to everyone.


Solution

  • I do not think there is anything evidently wrong with your design. I find it perfectly valid. There are few things that I would take into account if I were you:

    • Reckless users may write code for the interface Vector thinking their implementations are always mutable.
    • Immutability typically means more objects and a performance penalty due to the need to put more and more objects in the heap and forces the garbage collection to do more work. If your application will need to do many "add" operations you may need to pay the price. But hey, that's the whole purpose of having a mutable version, right?
    • Also, if you are writing for a multithreading environment, you will still need to synchronize access to share variables of type Vector when you are not sure of implementation above all if you want to ensure that the implementation can be switched without consequences. This, again, proves that it can be hard to write code oblivious of implementation details.
    • Although I argued a bit with @Paulo Eberman in other post, I do believe he is totally right. I think it is best to have two separate interfaces, one for immutable objects, and one for mutable (which could extend this latter).

    Of course most of this points are arguable, these are just my opinions.