我有一个班级X
。它有两个属性a
和b
。在某些情况下,X
的对象的相等性将基于a
的相等性,有些基于b
的相等性。我想知道对这些数据建模的最佳方法。
我不能基于某个标志简单地使用两个等于函数,因为我使用了很多集合和列表,所以我必须覆盖equals()
。这就是我的想法:
接口X
,有两个实现Xa
和Xb
。问题是,我需要在Xa
和Xb
之间进行转换,我希望有数百个实例,因此创建新副本会很昂贵。
由于预计大部分时间都会基于a
进行相等,因此请equals()
比较a
。当需要基于b
的相等时,只需为它编写一个单独的方法。问题是,我必须重新发明轮子来比较集合和列表。
上述的优点和缺点是什么?还有其他选择吗?
班级X
首先是否存在缺陷?我能以更好的方式实现这个吗?
答案 0 :(得分:3)
我想建议的第一件事是考虑重新设计对象层次结构。你所描述的情况听起来不是很干净,尽管我们对你试图根据你提供的信息建模的实际问题知之甚少。
根据你所说的坚定的要求,我可以想到以下 - 不是特别漂亮 - 解决方案。基本思想是X
对象的每个实例都会获得一个告诉其“性别”的标志。然后,在性别之间进行转换仅仅是分配一个单词的问题。但请注意,这也会使对象大小增加一个字。如果您有许多小对象,则额外的开销可能很大。 (在下面的玩具示例中,它高达三分之一,在这种情况下,我肯定更喜欢在需要时创建Xa
或Xb
类型的新对象。)取决于多么昂贵你的其他相等比较和哈希码计算,案例选择的额外开销也可能是显而易见的,尽管可能是可以接受的。
下面的课程是精心设计的,它符合我所知道的所有合同,可以在任何收藏中使用,并可以来回自由转换。但是,当对象包含在任何集合中时,不得触及对象的性别,集合可能只包含特定性别的X
个。正如您所看到的,我们正逐渐偏离面向对象并且必须管理我们自己的不变量。编译器无法帮助我们执行它们。这应该足以引起一个大红旗。
public final class X implements Comparable<X> {
public static enum Genders { A, B };
private Genders gender;
private final String a;
private final Integer b;
public X(final String a, final Integer b, final Genders gender) {
if (a == null) {
throw new NullPointerException("a");
}
if (b == null) {
throw new NullPointerException("b");
}
if (gender == null) {
throw new NullPointerException("gender");
}
this.a = a;
this.b = b;
this.gender = gender;
}
public Genders getGender() {
return this.gender;
}
public void setGender(final Genders gender) {
if (gender == null) {
throw new NullPointerException("gender");
}
this.gender = gender;
}
@Override
public boolean equals(final Object other) {
if (other instanceof X) {
final X otherX = (X) other;
if (this.gender == otherX.gender) {
switch (this.gender) {
case A:
return this.a.equals(otherX.a);
case B:
return this.b.equals(otherX.b);
default:
throw new AssertionError("unexpected gender");
}
}
}
return false;
}
@Override
public int hashCode() {
switch (this.gender) {
case A:
return this.a.hashCode();
case B:
return this.b.hashCode();
default:
throw new AssertionError("unexpected gender");
}
}
@Override
public int compareTo(final X other) {
// It seems acceptable to allow the case that
// this.gender != other.gender here.
switch (this.gender) {
case A:
return this.a.compareTo(other.a);
case B:
return this.b.compareTo(other.b);
default:
throw new AssertionError("unexpected gender");
}
}
@Override
public String toString() {
return String.format("{a: \"%s\", b: %d, gender: %s}",
this.a, this.b, this.gender);
}
}
这是一个小型演示如何使用该类型。
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public final class Main {
public static void main(final String[] args) {
final Set<X> theAs = new HashSet<>();
final Set<X> theBs = new TreeSet<>();
theAs.add(new X("alpha", 1, X.Genders.A));
theAs.add(new X("beta", 1, X.Genders.A));
theAs.add(new X("gamma", 2, X.Genders.A));
theAs.add(new X("delta", 2, X.Genders.A));
System.out.println("These are the As:\n");
for (final X x : theAs) {
System.out.println(x);
}
System.out.println();
{
final Iterator<X> iter = theAs.iterator();
while (iter.hasNext()) {
final X x = iter.next();
iter.remove(); // remove before changing gender
x.setGender(X.Genders.B);
theBs.add(x);
}
}
theBs.add(new X("alpha", 3, X.Genders.B));
theBs.add(new X("alpha", 4, X.Genders.B));
System.out.println("These are the Bs:\n");
for (final X x : theBs) {
System.out.println(x);
}
}
}
输出:
These are the As:
{a: "alpha", b: 1, gender: A}
{a: "delta", b: 2, gender: A}
{a: "beta", b: 1, gender: A}
{a: "gamma", b: 2, gender: A}
These are the Bs:
{a: "alpha", b: 1, gender: B}
{a: "delta", b: 2, gender: B}
{a: "alpha", b: 3, gender: B}
{a: "alpha", b: 4, gender: B}
如果你可以使用单个new
每个对象“转换”的开销来生活(而且我很确定你可以),那么使用{{3}的更清晰且不易出错的解决方案}}
让我们首先为您的类型定义一个接口。 (你的可能比这个玩具例子要复杂得多。)
public interface X {
public String getA();
public Integer getB();
}
接下来,我们提供了该接口的基本实现,除了在比较中采取立场之外,它还可以执行所有操作。请注意,该类是(可以)不可变的(特别是final
)。由于我没有覆盖equals
和hashCode
,甚至不打算实现Comparable
,因此这个“基类”的实例将具有从Object
继承的身份比较语义。这正是我们想要的(见后文)。
public final class BasicX implements X {
private final String a;
private final Integer b;
public BasicX(final String a, final Integer b) {
if (a == null) {
throw new NullPointerException("a");
}
if (b == null) {
throw new NullPointerException("b");
}
this.a = a;
this.b = b;
}
@Override
public String getA() {
return this.a;
}
@Override
public Integer getB() {
return this.b;
}
@Override
public String toString() {
return String.format("{a: \"%s\", b: %d}", this.a, this.b);
}
// Note: No implementation of equals() and hasCode().
}
有了所有业务逻辑,我们现在可以转向我们的装饰器。我们将定义其中两个:Xa
和Xb
。他们会将所有内容(在这个人为的示例中并不多)委托给其包含的X
实例,只是它们会提供适当的equals
和hashCode
实现并实现{{1} }。
由于两个装饰器的委托逻辑相同,我将公共代码分解为一个中间包 - 私有类。
Comparable
这会将abstract class DecoratedX implements X {
private final X x;
protected DecoratedX(final X x) {
if (x == null) {
throw new NullPointerException("x");
}
this.x = x;
}
protected final X getX() {
return this.x;
}
@Override
public final String getA() {
return this.x.getA();
}
@Override
public final Integer getB() {
return this.x.getB();
}
@Override
public final String toString() {
return this.x.toString();
}
}
和Xa
内的代码简化为比较逻辑,这在每个类中都是唯一的。请注意,Xb
和Xa
可以是Xb
。
final
我可能会用public final class Xa extends DecoratedX implements X, Comparable<Xa> {
public Xa(final X x) {
super(x);
}
@Override
public boolean equals(final Object other) {
if (other instanceof Xa) {
final Xa otherXa = (Xa) other;
return this.getA().equals(otherXa.getA());
}
return false;
}
@Override
public int hashCode() {
return this.getA().hashCode();
}
@Override
public int compareTo(final Xa other) {
return this.getA().compareTo(other.getA());
}
}
的(通常有些重复的)代码来惹恼你,但为了完整起见,这里是。
Xb
然后我们走了。把它们放在一起,我们可以做比以前更酷的事情。请注意我们现在可以在三个不同的集合中同时使用不同的比较语义将相同的对象(尽管在两种情况下包装(装饰))。
final class Xb extends DecoratedX implements X, Comparable<Xb> {
public Xb(final X x) {
super(x);
}
@Override
public boolean equals(final Object other) {
if (other instanceof Xb) {
final Xb otherXb = (Xb) other;
return this.getB().equals(otherXb.getB());
}
return false;
}
@Override
public int hashCode() {
return this.getB().hashCode();
}
@Override
public int compareTo(final Xb other) {
return this.getB().compareTo(other.getB());
}
}
输出:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public final class Main {
public static void main(final String[] args) {
final List<X> theXs = new ArrayList<>();
final Set<Xa> theXas = new HashSet<>();
final Set<Xb> theXbs = new TreeSet<>();
theXs.add(new BasicX("alpha", 1));
theXs.add(new BasicX("alpha", 1));
theXs.add(new BasicX("beta", 2));
theXs.add(new BasicX("beta", 3));
theXs.add(new BasicX("gamma", 2));
theXs.add(new BasicX("delta", 3));
for (final X x : theXs) {
theXas.add(new Xa(x));
theXbs.add(new Xb(x));
}
System.out.println("These are the As:\n");
for (final X x : theXas) {
System.out.println(x);
}
System.out.println();
System.out.println("These are the Bs:\n");
for (final X x : theXbs) {
System.out.println(x);
}
}
}
另请注意,此设计是类型安全的:编译器不会让我们在These are the As:
{a: "alpha", b: 1}
{a: "delta", b: 3}
{a: "beta", b: 2}
{a: "gamma", b: 2}
These are the Bs:
{a: "alpha", b: 1}
{a: "beta", b: 2}
{a: "beta", b: 3}
的集合中拥有Xb
对象。在示例中,我直接从Xa
创建了Xa
和Xb
。如果您想“将BasicX
变成Xa
”,反之亦然,那么代码当然是
Xb
和
Xb a2b(final Xa xa) {
return new Xb(xa.getX());
}
反过来。您必须使Xa b2a(final Xb xb) {
return new Xa(xb.getX());
}
方法DecoratedX.getX()
才能实现此目的。 (从技术上讲,你也可以将public
粘贴到Xa
中:毕竟它是 Xb
。虽然这样做完全有效,但在装饰模式的其他应用,无用的间接层很快会在这种情况下变得令人讨厌,并且很容易避免。)