2010-03-17 98 views
18

這裏是我的樣品抽象單例類:如何在Java中實現抽象單例類?

public abstract class A { 
    protected static A instance; 
    public static A getInstance() { 
     return instance; 
    } 
    //...rest of my abstract methods... 
} 

這裏是具體的實現:

public class B extends A { 
    private B() { } 
    static { 
     instance = new B(); 
    } 
    //...implementations of my abstract methods... 
} 

可惜我不能獲得B類執行靜態代碼,所以實例變量永遠不會被設置。我曾經嘗試這樣做:

Class c = B.class; 
A.getInstance() - returns null; 

ClassLoader.getSystemClassLoader().loadClass("B"); 
A.getInstance() - return null; 

運行這兩個在Eclipse調試器中的靜態代碼永遠不會被執行。我能找到的執行靜態代碼的唯一方法是將B構造函數的可訪問性更改爲public,然後調用它。

我在Ubuntu 32bit上使用sun-java6-jre來運行這些測試。

回答

19

摘要Singleton?對我來說聽起來不可行。 Singleton模式需要一個private構造函數,這已經使得子類化不可能。你需要重新考慮你的設計。 Abstract Factory pattern可能更適合於特定的目的。

+0

Singleton中的私有構造函數的目的是爲了防止其他人實例化它。我已經在這裏實現了 - 你不能實例化一個抽象類,並且子類有一個私有構造函數。 – Simon 2010-03-17 00:08:07

+0

它不會強制子類僅爲抽象類型,只有私有構造函數。 – BalusC 2010-03-17 00:13:15

+0

Singleton不需要_require_一個私有構造函數(請參閱我的答案,以便用公共構造函數解決此問題)。 – ryvantage 2015-09-13 18:26:42

4

A.getInstance()將永遠不會調用派生實例,因爲它是靜態綁定的。

我會從實際對象本身中分離對象的創建,並創建一個適當的factory返回一個特定的類類型。目前尚不清楚你是如何參數化的,給出你的示例代碼 - 是通過某個參數進行參數化還是靜態類選擇?

你可能想重新考慮單身人士,順便說一句。這是一個常見的反模式並且使得測試(特別是)痛苦,因爲被測試的類將把它們自己的那個類的實例作爲單例提供。您不能提供虛擬實現,也不能爲每個測試(輕鬆)創建新實例。

+0

A.getInstance()不需要調用派生實例,因爲它返回實例變量。派生實例(類B)設置實例變量。除了靜態代碼塊沒有運行。 如果我不能解決這個問題,那麼Factory模式可能是我唯一的選擇。 – Simon 2010-03-17 00:16:02

4

單身人士是一種yucky。摘要堅持繼承,如果可能的話,你往往不想avoid。總的來說,我會重新考慮你所要做的是否是simplest possible way,如果是的話,那麼一定要使用工廠而不是單身(單身人士很難在unit tests中替代,而工廠可以被輕鬆地替換測試實例)。

一旦你開始將它作爲一個工廠來實現,抽象的東西就會自我排序(或者它顯然是必要的,或者它可能容易取代接口)。

+0

+1指出單身人士是反模式。 – 2010-03-17 04:59:56

+1

單身人士也有自己的位置,但無論你在做什麼,通常都會有更好的設計模式。日誌可能是我可以爲單身人士想到的最好例子 - 這樣您就不必傳遞變量並可以從任何地方訪問它。 – 2013-04-17 19:30:01

+0

注射可以使日誌變得更好。單例日誌不能很容易地告訴你該行來自哪個類,而Log l = new Log(this.getClass())或其某個變量可以。注射它只是@inject日誌或類似的東西。即使是伐木,單身也很難測試。再次單身人士只是最糟糕的選擇(但承認是可用的) – 2013-04-17 21:42:29

2

除了其他問題所指出的那樣,具有Ainstance場意味着你只能在整個虛擬機一個單。如果你也有:

public class C extends A { 
    private C() { } 
    static { 
     instance = new C(); 
    } 
    //...implementations of my abstract methods... 
} 

...然後取其BC被載入最後會成功,其他的單一實例都將丟失。

這只是一個不好的做事方式。

+0

謝謝 - 和其他人說同樣的事情。這突出表明我真正的問題是我如何選擇我應該公開的接口的幾個實現中的哪一個。工廠或外觀看起來很不錯。 – Simon 2010-03-17 00:35:52

8

從你的文章中可以看出,即使沒有明確說明,聽起來像是你想要抽象類扮演兩種不同的角色。 的角色是:

  • 的 (單)服務,它可以有 多個替代 實現抽象工廠角色,
  • 服務 接口的作用,

加上你想要的服務在整個系列的類中是單例執行的「單線態」,由於某些原因,緩存服務實例是不夠的。

這很好。

有人會說它聞起來非常糟糕,因爲「違反了關注點分離」和「單身和單元測試不一致」。

別人會說它是好的,因爲你給家庭本身實例化正確的孩子的責任,也暴露更流利的界面整體,因爲你不需要調解一個工廠,除了暴露靜態方法。

什麼是錯誤的是,你想讓孩子們負責選擇父廠方法應該返回什麼樣的實現。 這是錯誤的設計方面,因爲你委託給所有的孩子什麼可以簡單地推到集中到抽象的超類,它也表明你正在混合在不同的上下文中使用的模式,抽象工廠(父母決定什麼家庭班級客戶將獲得)和工廠方法(兒童工廠選擇客戶將得到什麼)。

工廠方法不僅不是必需的,而且工廠方法也是不可能的,因爲它集中於實現或重寫「實例」方法。對於靜態方法和構造函數都沒有這樣的重寫。

因此,回到抽象單例的初始好的或壞的想法,選擇哪種行爲暴露有幾種方法來解決初始問題, 可能是以下幾種方式,看起來不好,但我想接近什麼您正在查找:

public abstract class A{ 
    public static A getInstance(){ 
     if (...) 
     return B.getInstance(); 
     return C.getInstance(); 
    } 

    public abstract void doSomething(); 

    public abstract void doSomethingElse(); 

} 

public class B extends A{ 
    private static B instance=new B(); 

    private B(){ 
    } 

    public static B getInstance(){ 
     return instance; 
    } 

    public void doSomething(){ 
     ... 
    } 
    ... 
} 

//do similarly for class C 

父母也可以使用反射。

更友好的測試和擴展友好的解決方案是簡單地讓孩子不是單身,但打包成一些內部包,你將文檔作爲「私人」和抽象的父母,可以暴露「單身模仿」靜態getInstance()和將緩存子實例強制客戶端始終獲得相同的服務實例。