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

发布于 2024-11-01 10:18:53 字数 2260 浏览 1 评论 0原文

我的问题涉及 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

Oo萌小芽oO 2024-11-08 10:18:53

作为 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 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.

通知家属抬走 2024-11-08 10:18:53

我不认为你的设计有任何明显的错误。我发现它完全有效。如果我是你,我会考虑以下几件事:

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

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

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.

我的痛♀有谁懂 2024-11-08 10:18:53

你的想法很好,但还不够完美。

你遗漏了泛型。

您假设算术运算(例如加法和减法)是为您的 Vector 所持有的类型定义的,这可能不是真的。 (泛型可能会对此有所帮助。)

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

完美的 API 应该有一个类似的 Matrix 类,因为您需要进行数学和物理的线性代数。

我会查看 Apache 的通用数学库以获取灵感。这是JAMA的继承人。我发现观察比我更好的人的成功设计和实现是一种很好的学习方法。

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.

云之铃。 2024-11-08 10:18:53

我觉得这个设计不太好。拥有可变算术对象并不好,即使您有它们明确标记为可变的。此外,我不会将向量运算放在向量类中。因为现在你只有加法和乘法,明天你会想要别的东西,你的班级会随着你添加这个或什么向量运算而不断成长。如果我是你,我会创建一个像这样的不可变向量

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

但实际上我不喜欢这里的可变性,因为它带来了不必要的并发症

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文