2011-07-21 74 views
4

想象一下,您有一個派生類,其中基類是不能修改的。基類有許多狀態(許多非常量私有成員)和許多構造函數,它們具有不同數量的參數來初始化狀態的某個子集(當然,子集的大小因構造函數而異)。在派生類構造函數(或工廠)中初始化基類的模式

現在我的派生類是一個非常輕量級的基類包裝。假設它沒有添加自己的狀態,只是稍微修改了幾個方法的行爲(可能會在調用super.originalMethod()時做一些額外的日誌記錄)。

我遇到的問題是我想獲取基類的一個對象,並使用相同的狀態創建它的「副本」,但是作爲我的派生類的實例。

這證明很困難。我不能調用基類的「最完整」構造函數,通過調用getters來傳遞源代碼中的所有狀態,因爲根據基類的構造方式,某些狀態值可能會被此構造方法拒絕。例如,您可以使用0-arg ctor創建一個默認對象,任何許多值都將爲null。但是,在ctor中傳遞空值以允許您指定這些值並不合法。

此外,上述方法是脆弱的,因爲如果對基類進行修改會增加更多的狀態,並且「更完整」的構造函數(或者不能在構造函數中設置的狀態,但只能通過訪問方法)被添加後,副本將不會再完成。

我想要的就像`clone(),而是初始化相同類型的新對象,初始化派生類的基類成員。我猜這樣的事情不存在。任何可能提供相同內容的模式建議?

請記住,我不能修改基類。如果可以的話,這會容易得多。

回答

4

如果可以覆蓋所有公共方法,您可以將源對象保存爲代表

Class D extends B 
    B src; 
    D(B src){ super(whatever); this.src=src; } 

    public method1(){ src.method1(); } 
+0

的確如此。缺點是類D在這種情況下有無用的(總是空/默認)成員。雖然很有意思,但它並沒有實現hoipolloi答案的接口問題。 – BeeOnRope

+0

我喜歡用單個句子描述看似複雜的問題的解決方案。 – gdbj

0

這看起來像Proxy的工作。 (也許,如果你谷歌,你可以找到更好的代理實現,但該標準是一個在我看來,良好的enuf。)

Implemnet的InvocationHandler這樣

class Handler implements InvocationHandler 
{ 
    private Thingie thingie ; 

    public Handler (Thingie thingie) 
    { 
     this . thingie = thingie ; 
    } 

    public Object invoke (Object proxy , Method method , Object [ ] args) thro 
ws Throwable 
    { 
     if (method . getName () . equals ("target")) 
      { 
       LOG . log (this) ; 
      } 
     return method . invoke (this . thingie , args) ; 
    } 
} 
+0

可惜,這只是作品,如果我想實現一個接口,在基類的條款 - 在這裏,我需要直接實現基類。 – BeeOnRope

+0

然後我想你想要http://commons.apache.org/proxy/。在我看來,它比其他選項更好,因爲你可以將原始對象和它的基類像黑盒子一樣對待。你不需要知道任何有關其內部工作的信息 - (1)爲你實施的工作減少; (2)如果基類發生變化,代理處理所有這些。 – emory

+0

@emory:如果OP可以避開使用合成,它會引入不必要的複雜性和依賴性。否則,我同意,它似乎代表一個優雅的解決方案(遠遠優於組合和繼承解決方案)。 – hoipolloi

-1

我不認爲你是由於繼承的作用方式,可以完全按照這種方式進行分配。假設您的基類是「A」類型的。您創建類型爲「B」的包裝類。您可以分配一個「B」的實例來鍵入「A」,但不能分配「A」的實例來鍵入「B」。

+0

我不確定這是相關的。賦值僅適用於引用(和原始類型),不能將任何對象的「實例」賦值給任何其他對象。 – BeeOnRope

3

贊同繼承的組合,也許通過創建一個包裝類(如下所示)。如果您的基類使用接口,您的包裝類可以實現相同的接口並將調用委託給基類(裝飾器)。

但是,在繼承中描述時沒有強有力的策略。就像您指出的那樣,即使您使用了反射來執行深層複製,實現可能也會改變。你打破了封裝,你的代碼將與基類密切相關。

public static void main(final String[] args) { 
    final Base base = new Base("Hello"); 
    base.printState(); // Prints "Hello" 
    final Wrapper wrapper = new Wrapper(base); 
    wrapper.printState(); // Prints "Wrapper says Hello" 
    wrapper.clone().printState(); // Prints "Wrapper says Hello" 
} 

private static class Wrapper { 

    private final Base base; 

    public Wrapper(final Base base) { 
     this.base = base; 
    } 

    public Wrapper clone() { 
     return new Wrapper(base); 
    } 

    public void printState() { 
     System.out.printf("Wrapper says "); 
     base.printState(); 
    } 
} 

private static class Base { 

    private Object state; 

    public Base(final Object state) { 
     if (state == null) { 
      throw new IllegalArgumentException("State cannot be null"); 
     } 
     this.state = state; 
    } 

    public void printState() { 
     System.out.println(state); 
    } 
} 
+0

是的,如果可以的話,我肯定會實現這種代理模式。不幸的是,我得到的基類是公共API,並沒有實現我可以實現的任何有趣的接口。 也就是說,我需要我的對象成爲基類型的一個實例,因爲我必須在我無法更改的方法中使用它。 – BeeOnRope

+0

@BeeOnRope:如果你受制於設計不佳的框架,那麼我認爲你運氣不好。有解決方案,但它們很脆弱,並且不會維持超類中的API更改。 – hoipolloi

+0

感謝您的全面回答! – BeeOnRope

1

正如其他人所指出的,很自然的會想到通過代表團解決這一點,並實現它的代理或裝飾者。處理這些模式的標準方法要求您在基礎上有一個接口而不是具體的類,就像Java的動態代理一樣。

但是,您可以使用cglibjavassist完成混凝土類類似的事情。

有了足夠的運行時JVM修補,可能通過上述之一或AspectJ,我認爲你甚至可以讓你的現有類實現一個新定義的接口。

休眠創造了所有的持久類代理而不需要他們實現一個接口,我相信它使用CGLIB來做到這一點。

+0

這非常有趣 - 可能是爲了我的目的而矯枉過正,但我​​會在未來考慮其他情況。 – BeeOnRope

0

如果基類不提供內置的克隆支持,則沒有任何真正的好方法。 IMHO,如果右模式是類分爲三類:

-1-類這從根本上不能有意義地被以任何方式克隆,而不斷裂類不變量。

-2-其可以在不破壞類不變量被克隆,但其可以被用來導出不能有意義地被克隆的其他類的類。

-3-可被克隆的類,它只被用於派生類可以被克隆的類。類型的

類-2-或-3-應該提供一個受保護的虛擬克隆方法,該方法將調用父的實現(如果其是一個),或Object.Clone(如果沒有父實現),然後做任何類特定的清理。類型-3-的類應提供一種公共克隆方法,該方法將調用虛擬方法並將結果轉換爲適當的類型。衍生自類型-2-的類型的可繼承類應該用受保護的克隆方法映射除函數外的其他內容。

如果沒有辦法將受保護的克隆方法添加到父類中,則無法構建可複製的派生類,這對於父類實現細節來說不會太脆弱。如果根據上述模式構建父類,克隆將可以在派生類中清晰地實現。

+0

在基類中有一個可訪問的clone()方法,但我不想克隆基類,而是我想創建一個具有與給定基類相同的基類狀態的派生類(不是給定的派生類)。 – BeeOnRope

+0

如果基類的克隆方法使用Object.Clone創建新對象,則在派生類對象上調用它將創建一個新的派生類對象。如果創建基類克隆方法的人使用複製構造函數,則創建克隆的唯一方法是派生類對象的狀態是自己重新實現複製構造函數的內容。 – supercat

+0

當然,但我不明白這與問題有何關聯。 我有一個類型爲「BaseClass」的對象,用一個可訪問的clone()方法調用它。我想創建一個類型爲「DerivedClass」的對象,其BaseClass狀態與b對象相同。 clone()'如何提供幫助?我不能在'b'上調用它,因爲我會得到一個'BaseClass'類型的對象。我不能在派生類的任何實例上調用它,因爲不存在(具有我想要的狀態)。 – BeeOnRope

1

我注意到有人推薦你使用組合和繼承(見下面這個反模式的例子)。

請做到這一點只能作爲最後的手段。除了引入冗餘狀態之外,您的子對象還會暴露完全忽略的狀態和行爲。這將導致一個非常誤導性的API。

public static void main(final String[] args) { 
    final Base base = new Base("Hello"); 
    base.printState(); // Prints "Hello" 
    final Wrapper wrapper = new Wrapper(base); 

    wrapper.changeState("Goodbye"); 

    wrapper.printState(); // Prints "Wrapper says Hello" 
    wrapper.clone().printState(); // Prints "Wrapper says Hello". 

    // It seems my state change was completely ignored. What a confusing API... 
} 

private static class Wrapper extends Base { 

    private final Base base; 

    public Wrapper(final Base base) { 
     super("Make something up; this state isn't used anyway"); 
     this.base = base; 
    } 

    public Wrapper clone() { 
     return new Wrapper(base); 
    } 

    public void printState() { 
     System.out.printf("Wrapper says "); 
     base.printState(); 
    } 
} 

private static class Base { 

    private Object state; 

    public Base(final Object state) { 
     if (state == null) { 
      throw new IllegalArgumentException("State cannot be null"); 
     } 
     this.state = state; 
    } 

    public void changeState(final Object state) { 
     this.state = state; 
    } 

    public void printState() { 
     System.out.println(state); 
    } 
} 

編輯:其實,只是不這樣做。永遠。這是一個可怕的,可怕的戰略。如果你無法管理與基類狀態的所有交互(這又是一個非常脆弱的解決方案),那麼將會發生非常糟糕的事情。例如,如果我修改基類,如下所示:

private static class Base { 

    ... 

    // A new method 
    public Object getState() { 
     return state; 
    } 

    ... 
} 

哦,親愛的...

final Wrapper wrapper = new Wrapper(new Base("Foo")); 
System.out.println(wrapper.getState()); // Prints "Make something up; this state isn't used anyway" 
+0

同意,但有時你的手試圖對現有代碼的結構。最後,我發現我可以通過在類路徑中加入我的.class類來覆蓋整個類,所以這讓我可以用一種稍微簡單的方式修補行爲。 – BeeOnRope

+0

...但我仍然會接受這個答案,因爲它只是我能看到的那個與給定的約束一起工作的答案。 – BeeOnRope

+0

@BeeOnRope:當classpath shenanigans被認爲是一種'不太冒險的方式'時,這是一種令人遺憾的事態!當我們的手綁在一起的時候,它很糟糕。出於興趣,你想與哪個框架合作?祝你好運! – hoipolloi

相關問題