2

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.

Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137
  • Add methods that don't add but instead return a new object? Not ideal! – rich Apr 16 '11 at 21:26
  • It's called a fluent interface, rich. It's all the rage. And it does add - look again. – duffymo Apr 16 '11 at 21:32
  • 2
    Having both a mutable and an immutable implementation of an interface introduces the problem that you can't rely on if an object is immutable without backtracking in your code. This becomes an even bigger problem if it's a 3:rd party library returning the object. You might end up having to treat all objects as potentially mutable, making the immutable implementation redundant. If you explain what problem you are trying to solve by having both mutable and immutable implementations of a vector interface, then we can possibly help you solve that with a cleaner design. – Buhb Apr 16 '11 at 22:56

4 Answers4

8

As a user of your Vector library, I would not like to have one add implementation which modifies the current Object and another add implementation (of the same interface) which returns a new one.

Better have a clear set of methods which do not modify the current object, and then have additional methods in the mutable vector which do modify the current object.

Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
  • If it is well documented in the interface contract that "add" returns the instance corresponding to the result of the operation and that this is the instance that should be used in subsequent calls it should not be a big deal, perhaps the name "add" may not be the most appropriate, but there is nothing arguably wrong in having two implementations of the interface where one is immutable and the other is not as long as they both satisfy the contract. It is true that reckless users may easily make mistakes by incorrectly using instances of the interface, since they should be oblivious of its type – Edwin Dalorzo Apr 16 '11 at 22:09
  • The problem with the mutability is more that I might want to reuse the original (unchanged) object for another calculation, not that I have to remember to use the return value. So at least make sure to provide a method `toImmutableVector()` which returns an immutable vector (might be `return this` in the immutable case). – Paŭlo Ebermann Apr 16 '11 at 22:25
  • @PAulo Eberman Don´t get me wrong, I think you are right. I am just trying to play devils advocate to make the argument more interesting. – Edwin Dalorzo Apr 16 '11 at 22:25
  • Indeed, I didn't mention them in the original post, but the methods toImmutableVector and toMutableVector do exist. When posting on SO, I always try to simplify at maximum in order to focus on the question; maybe I simplified too much. – Olivier Grégoire Apr 16 '11 at 22:30
2

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.

Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205
0

Your idea is fine, but it's hardly perfect.

You've left out generics.

You assume that arithmetic operations such as addition and subtraction are defined for the types your Vector is holding, which may not be true. (Generics might help with that.)

I don't know how useful an immutable vector is in the context of mathematics and physics.

A perfect API would have an analogous Matrix class, since you'll need to do linear algebra for math and physics.

I'd have a look at Apache's common math library for inspiration. It's the heir to JAMA. I find that looking at successful designs and implementations by my betters is a good way to learn.

duffymo
  • 305,152
  • 44
  • 369
  • 561
  • The vector here is an example. I could choose to rewrite the List interface. Please focus on the way to handle both Immutable/Mutable objects without UnsupportedOperationException. -- How can Generics help me in this case? I would like to add a Vector to another. I don't care if the parameter is mutable or not. I really fail to see where Generics would help me here... – Olivier Grégoire Apr 16 '11 at 21:45
0

I think this design is not very good. Having mutable arithmetical objects is not good if even you have them explicitly marked as mutable. Additionally, I wouldn't put vector operations in the class vector. Because now you have only addition and multiplication and tomorrow you will want something else and your class will grow and grow as you will add this or what vector operation. If I were you, I would create an immutable vector like this

public class Vector {

    private Double X;
    private Double Y;

    public Vector(Double x, Double y) {
        X = x;
        Y = y;
    }

    public Double getX() {
        return X;
    }

    public Double getY() {
        return Y;
    }
}

and then I would create a class for doing basic vector operations:

public class BaseVectorAlgebra {

    public static Vector add(Vector arg1, Vector arg2) {
        return new Vector(arg1.getX() + arg2.getX(), arg1.getY() + arg2.getY());
    }

}

This way you will have an easy way to extend the system without touching existing classes and without introducing mutability, which just complicate things.

UPDATE:

If you still want to go with mutable vectors, then I would add SetX and SetY setters into Vector class, but put mutability decision into BaseVectorAlgebra like this:

public static Vector addInto(Vector arg1, Vector arg2) {
    arg1.setX(arg1.getX() + arg2.getX());
    arg1.setY(arg1.getY() + arg2.getY());

    return arg1;
}

But really I don't like mutability here as it introduces unnecessary complications

jontejj
  • 2,800
  • 1
  • 25
  • 27
Vladislav Rastrusny
  • 29,378
  • 23
  • 95
  • 156
  • I don't really like to have mutable vectors as well. But the thing is that for memory/performance issue, they might be needed. In this case, I'd like to have a robust API _and_ good performances. Hence the question. – Olivier Grégoire Apr 16 '11 at 22:05
  • @ogregoire Well, then try to consider my last example. Vector shouldn't know about any mathematics over it. This is my opinion ;) – Vladislav Rastrusny Apr 16 '11 at 22:17