Java中不可变和可变对象的设计

时间:2011-04-16 21:19:48

标签: java class-design immutability mutable

我的问题涉及API设计。

假设我正在设计一个矢量(数学/物理意义)。我想要一个不可变的实现和一个可变的实现。

然后我的矢量看起来像这样:

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();
}

我想知道如何确保同时具有可变和不可变的实现。我真的不喜欢java.util.List的方法(默认允许可变性)和Guava不可变实现的UnsupportedOperationException()。

如何使用这两种实现设计“完美”界面或抽象类Vector?

我考虑过这样的事情:

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;
  }
  ...
}

总而言之,我想检查这种方法是否存在明显的设计缺陷,它们是什么以及我应该做些什么来解决这些问题?


注意:“向量”内容是更一般用例的示例。为了我的问题,我可以选择重写List接口或其他任何东西。请关注更一般的用例。


最终选择,在下面的答案之后,基于Joda-time,如有人解释但现在编辑:

/** 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) {...}
  ...
}

我将Vector从接口更改为抽象类,因为基本上矢量不应该是其他任何东西。

谢谢大家。

4 个答案:

答案 0 :(得分:7)

作为Vector库的用户,我不希望有一个add实现修改当前Object和另一个add实现(同一个接口)返回一个新实现。 / p>

最好有一组明确的方法不修改当前对象,然后在可变向量中有其他方法来修改当前对象。

答案 1 :(得分:2)

我认为您的设计没有任何明显错误。我发现它完全有效。如果我是你,我会考虑很少的事情:

  • 鲁莽的用户可能会为此编写代码 界面矢量思考他们的 实现总是可变的。
  • 不可变性通常意味着更多的对象和性能损失,因为需要将越来越多的对象放入堆中并强制垃圾收集执行更多工作。如果您的应用程序需要执行许多“添加”操作,您可能需要付出代价。但是,嘿,这就是拥有一个可变版本的全部目的,对吗?
  • 此外,如果您正在为多线程环境编写代码,那么如果您想确保可以切换实现而不会产生任何后果,那么当您最不确定实现时,仍然需要同步访问以共享Vector类型的变量。这再次证明,编写代码时无法忽略实现细节。
  • 虽然我在其他帖子中与@Paulo Eberman有点争论,但我相信他是完全正确的。我认为最好有两个独立的接口,一个用于不可变对象,另一个用于mutable(可以扩展后者)。

当然,大多数这些观点都是有争议的,这些只是我的观点。

答案 2 :(得分:0)

你的想法很好,但它并不完美。

你遗漏了泛型。

您假设为Vector所持有的类型定义了加法和减法等算术运算,这可能不正确。 (泛型可能会有所帮助。)

我不知道不可变向量在数学和物理学的背景下有多么有用。

完美的API会有一个类似的Matrix类,因为你需要为数学和物理做线性代数。

我将看看Apache的常用数学库以获取灵感。它是JAMA的继承人。我发现,通过我的优秀人员查看成功的设计和实施是一种很好的学习方式。

答案 3 :(得分:0)

我觉得这个设计不是很好。拥有可变的算术对象is not good,即使你明确标记为可变对象。另外,我不会将向量操作放在类向量中。因为现在你只有加法和乘法,明天你会想要别的东西,你的类会增长和增长,因为你会添加这个或什么向量操作。如果我是你,我会像这样创建一个不可变的向量

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;
    }
}

然后我会创建一个用于执行基本向量操作的类:

public class BaseVectorAlgebra {

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

}

通过这种方式,您可以轻松扩展系统,而无需触及现有类,也不会引入可变性,这会使事情变得复杂。

<强>更新

如果您仍想使用可变向量,那么我会将SetX和SetY setter添加到Vector类中,但是将可变性决策放入BaseVectorAlgebra中,如下所示:

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

    return arg1;
}

但我真的不喜欢这里的可变性,因为它引入了不必要的并发症