Java中不可变和可变对象的设计
我的问题涉及 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 接口或其他任何东西。请关注更一般的用例。
最终选择,在下面的答案之后,基于乔达时间,正如有人解释但现在编辑的:
/** 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 从接口更改为抽象类,因为基本上向量不应该是其他任何东西。
谢谢大家。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
发布评论
评论(4)
我不认为你的设计有任何明显的错误。我发现它完全有效。如果我是你,我会考虑以下几件事:
- 鲁莽的用户可能会为
接口向量思考他们的
实现总是可变的。 - 不变性通常意味着更多的对象和性能损失,因为需要将越来越多的对象放入堆中,并迫使垃圾收集做更多的工作。如果您的应用程序需要执行许多“添加”操作,您可能需要付出代价。但是,嘿,这就是可变版本的全部目的,对吧?
- 另外,如果您正在为多线程环境编写代码,并且您想确保可以在不产生任何后果的情况下切换实现,那么当您首先不确定实现时,您仍然需要同步对共享 Vector 类型变量的访问。这再次证明,编写不考虑实现细节的代码可能很困难。
- 尽管我在其他帖子中与@Paulo Eberman 争论了一些,但我确实相信他是完全正确的。我认为最好有两个单独的接口,一个用于不可变对象,一个用于可变对象(可以扩展后者)。
当然,大部分观点都是有争议的,这些只是我的观点。
我觉得这个设计不太好。拥有可变算术对象并不好,即使您有它们明确标记为可变的。此外,我不会将向量运算放在向量类中。因为现在你只有加法和乘法,明天你会想要别的东西,你的班级会随着你添加这个或什么向量运算而不断成长。如果我是你,我会创建一个像这样的不可变向量
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 设置器添加到 Vector 类中,但将可变性决策放入 BaseVectorAlgebra 中,如下所示:
public static Vector addInto(Vector arg1, Vector arg2) {
arg1.setX(arg1.getX() + arg2.getX());
arg1.setY(arg1.getY() + arg2.getY());
return arg1;
}
但实际上我不喜欢这里的可变性,因为它带来了不必要的并发症
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
作为 Vector 库的用户,我不希望有一个修改当前对象的
add
实现和另一个返回新对象的add
实现(同一接口)一。最好有一组明确的不修改当前对象的方法,然后在可变向量中具有修改当前对象的其他方法。
As a user of your Vector library, I would not like to have one
add
implementation which modifies the current Object and anotheradd
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.