Java中数据的双重性

时间:2015-03-29 20:46:12

标签: java

我有一个班级X。它有两个属性ab。在某些情况下,X的对象的相等性将基于a的相等性,有些基于b的相等性。我想知道对这些数据建模的最佳方法。

我不能基于某个标志简单地使用两个等于函数,因为我使用了很多集合和列表,所以我必须覆盖equals()。这就是我的想法:

  1. 接口X,有两个实现XaXb。问题是,我需要在XaXb之间进行转换,我希望有数百个实例,因此创建新副本会很昂贵。

  2. 由于预计大部分时间都会基于a进行相等,因此请equals()比较a。当需要基于b的相等时,只需为它编写一个单独的方法。问题是,我必须重新发明轮子来比较集合和列表。

  3. 上述的优点和缺点是什么?还有其他选择吗?

    班级X首先是否存在缺陷?我能以更好的方式实现这个吗?

1 个答案:

答案 0 :(得分:3)

解决方案0:重构

我想建议的第一件事是考虑重新设计对象层次结构。你所描述的情况听起来不是很干净,尽管我们对你试图根据你提供的信息建模的实际问题知之甚少。

解决方案1:“切换”多态性

根据你所说的坚定的要求,我可以想到以下 - 不是特别漂亮 - 解决方案。基本思想是X对象的每个实例都会获得一个告诉其“性别”的标志。然后,在性别之间进行转换仅仅是分配一个单词的问题。但请注意,这也会使对象大小增加一个字。如果您有许多小对象,则额外的开销可能很大。 (在下面的玩具示例中,它高达三分之一,在这种情况下,我肯定更喜欢在需要时创建XaXb类型的新对象。)取决于多么昂贵你的其他相等比较和哈希码计算,案例选择的额外开销也可能是显而易见的,尽管可能是可以接受的。

下面的课程是精心设计的,它符合我所知道的所有合同,可以在任何收藏中使用,并可以来回自由转换。但是,当对象包含在任何集合中时,不得触及对象的性别,集合可能只包含特定性别的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}

解决方案2:装饰器模式

如果你可以使用单个new每个对象“转换”的开销来生活(而且我很确定你可以),那么使用{{3}的更清晰且不易出错的解决方案}}

让我们首先为您的类型定义一个接口。 (你的可能比这个玩具例子要复杂得多。)

public interface X {

    public String getA();

    public Integer getB();
}

接下来,我们提供了该接口的基本实现,除了在比较中采取立场之外,它还可以执行所有操作。请注意,该类是(可以)不可变的(特别是final)。由于我没有覆盖equalshashCode,甚至不打算实现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().
}

有了所有业务逻辑,我们现在可以转向我们的装饰器。我们将定义其中两个:XaXb。他们会将所有内容(在这个人为的示例中并不多)委托给其包含的X实例,只是它们会提供适当的equalshashCode实现并实现{{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内的代码简化为比较逻辑,这在每个类中都是唯一的。请注意,XbXa可以是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创建了XaXb。如果您想“将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。虽然这样做完全有效,但在装饰模式的其他应用,无用的间接层很快会在这种情况下变得令人讨厌,并且很容易避免。)