[英]Dual nature of data in Java
我有一個X
班。 它有兩個屬性a
和b
。 在某些情況下, X
的對象的相等性將基於a
相等性,並且在某些情況下基於b
相等性。 我想知道建模這些數據的最佳方法。
我不能簡單地基於某個標志有兩個等於函數,因為我使用了很多集合和列表,所以我必須重寫equals()
。 這就是我的想法:
接口X
,有兩個實現Xa
和Xb
。 問題是,我需要在Xa
和Xb
之間進行轉換,我希望有數百個實例,因此創建新副本會很昂貴。
由於基於a
相等性預計會在大多數時間發生,因此實現equals()
比較a
。 當需要基於b
相等時,只需為它編寫一個單獨的方法。 問題是,我必須重新發明輪子來比較集合和列表。
以上的優點和缺點是什么? 還有其他選擇嗎?
X
級是否有缺陷? 我能以更好的方式實現這個嗎?
我想建議的第一件事是考慮重新設計對象層次結構。 你所描述的情況聽起來不是很干凈,盡管我們對你試圖根據你提供的信息建模的實際問題知之甚少。
把你所說的作為堅定的要求,我可以想到以下 - 不是特別漂亮 - 解決方案。 基本思想是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
每個對象“轉換”的開銷(並且我很確定你可以),那么使用裝飾器就會更清晰,更容易出錯。
我們首先為您的類型定義一個接口。 (你的可能會比這個玩具例子復雜得多。)
public interface X {
public String getA();
public Integer getB();
}
接下來,我們提供該接口的基本實現,除了在比較中采取立場之外,它將執行所有操作。 請注意,該類是(可以)不可變的(特別是final
的)。 由於我沒有覆蓋equals
和hashCode
,甚至不打算實現Comparable
,因此這個“base”類的實例將具有從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
實現並實現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
的代碼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}
另請注意,此設計是類型安全的:編譯器不會讓我們在Xa
集合中擁有Xb
對象。 在這個例子中,我直接從BasicX
創建了Xa
和Xb
。 如果你想“將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
:畢竟它是一個 X
雖然這完全有效,並且適用於裝飾器模式的其他應用程序,但在這種情況下,無用的間接層很快會變得令人討厭並且很容易避免。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.