2017-10-16 108 views
-1

我正在尋找一種方法來實現一個抽象類(或有效抽象),只強制每個子類的一個實例。如何在Java中實現一個子類Singleton

我相當肯定這對使用Factory實現來說非常簡單,但我很想知道是否可以在不知道所有子類類型的情況下完成,即通用單例實施程序類。

現在我大部分時間都只是想着這樣的想法,所以我沒有在尋找反饋來質疑這裏的設計選擇。

我正在使用的語言是Java,但現在我不一定擔心實現細節,除非在Java中不可能,那麼當然,提供證據表明它是不可能的。

+3

不可能在沒有私有構造函數的情況下強制實施Singleton模式,這使得外部子類化變得不可能。 – shmosel

+0

看看[問]和[幫助/話題]。這種開放式問題並不是一個很好的解決方案。 – pvg

+1

'提供證據表明這是不可能的',這不是如何工作......正如@pvg所說,這不是[help/on-topic],即使它是你的舉證責任。 – Oleg

回答

0

我想知道你在做什麼。有幾種可能性值得思考,並且知道這個標題在哪裏可能會有所幫助。

選項1

所以,你可以嘗試使用enum類型的抽象基類。每個枚舉常量然後由該語言保證爲單例。枚舉可以有常量實現的抽象方法。如果你有很多實現常量和很多抽象方法來實現,這將會起作用,但編譯單元變得非常大並且很難導航。你當然可以把一些工作委託給輔助類,如果它開始失控的話。

選項2

你可以做的就是讓基類的構造函數,以檢查它的實際類型,並將其存儲在一個靜態的HashSet(或類似)。如果一個條目已經存在,那麼你有兩個同一個單例的實例。像

public abstract class BaseClass { 
    private static HashSet<Class<?>> instances = new HashSet<>(); 

    protected BaseClass() { 
     checkInstances(); 
    } 

    private synchronized void checkInstances() { 
     boolean duplicate = instances.add(getClass()); 

     if (duplicate) { 
      throw new RuntimeException("Duplicate class " + getClass().getName()); 
     } 
    } 
} 

此方法的缺點是東西在運行時出現錯誤,代碼不是特別漂亮,因爲你可以看到你可能需要考慮集合

選項3同步

您的最終選擇根本不是要求基類強制執行此限制。它應該可能是派生類的工作來決定它們是否是單例。派生類中的私有構造函數是最簡單的方法。

結論

個人而言,我會執行選項1或選項3,你不會得到運行時的故障。

+0

我想出了類似於你的「選項2」的東西,但我認爲它比我想要的更麻煩,而且我不認爲對於以前沒有使用過這種東西的人來說這是完全清楚的。所以我可能會訴諸不執行單身。 – jdodle

+0

我同意 - 選項2醜陋。恕我直言,強制單身人士應該是單身人士自己的工作,而不是它擴展的類別。 – Stormcloud

0

首先,一個通用的單例沒有意義。
父類不應負責檢索和管理其子類的實例。
它在兩種方式(父母 - >孩子和孩子 - >父母)中建立了強大的聯結。其次,正如shmosel所說的,子類化一個單獨的(沒有特殊的神器)是不可能的。
單例模式的關鍵是缺乏在單例類之外實例化類的能力,因此不需要提供公共構造函數。
在這些情況下,如何繼承單例類?

要允許繼承一個singleton類,您必須有一個公共構造函數,同時確保您沒有多於一個該類的實例。 (例如Spring的控制容器的反轉可能會這樣做)(這是特殊工件的一個例子)。


作爲一個方面說明,我不認爲訪問修飾符的package-private改性劑,可以讓子類單身,但它的侷限性是單身就單身只能在外面的包裝調整等。

+0

我*想*他想寫一組單個的基類。 – Stormcloud

+0

OP說:「我正在尋找一種實現抽象類(或有效抽象)的方法,只強制每個子類的一個實例。」所以我得出結論,抽象類處理子類的實例。 – davidxxx

+0

我同意。我不得不承認,我花了一些時間來思考這個基類的目的是什麼。我仍然認爲這是檢查單身人士的錯誤類別。派生類中的私有構造函數將對此進行排序。 – Stormcloud

-1

我想說,那個單身人士是不好的。但是發現這個問題很有趣,所以我創建了你想要的東西。 下面是代碼

public static abstract class SingletonBase { 
     private static HashSet<SingletonBase> instances = new HashSet<>(); 

     { 
      for (SingletonBase sb : instances) { 
       if (sb.getClass() == this.getClass()) throw new RuntimeException("there is already 1 instance"); 
      } 
     } 

     public static <E> E getInstance(Class<E> clazz) { 
      if (!SingletonBase.class.isAssignableFrom(clazz)) { 
       throw new RuntimeException(); 
      } 
      for (SingletonBase sb : instances) { 
       if (sb.getClass() == clazz) return (E) sb; 
      } 

      try { 
       return clazz.newInstance(); 
      } catch (InstantiationException e) { 
       e.printStackTrace(); 
      } catch (IllegalAccessException e) { 
       e.printStackTrace(); 
      } 
      return null; 

     } 

     private SingletonBase() { 
      instances.add(this); 
     } 

    } 
    static class SingletonTest extends SingletonBase{ 

    } 

    static class SecondSingletonTest extends SingletonBase{ 

    } 

    public static void main(String[] args) { 
     for(int i=0;i<=10;i++) 
      System.out.println( SingletonBase.getInstance(SingletonTest.class)); 
     for(int i=0;i<=10;i++) 
      System.out.println( SingletonBase.getInstance(SecondSingletonTest.class)); 
     //throws exception, because we try to create second instance here 
     new SingletonTest(); 
    } 

有一些問題與創建通用類的做法,在這裏解決: 首先,你不能創建一個以上的實例,因此基類有跟蹤所有的實例,以及當你嘗試使用new創建另一個時,它會拋出異常。其次,你需要爲特定的類獲取實例。如果你不想這樣創建實例:

SingletonBase.getInstance(SecondSingletonTest.class) 

你可以這樣建立子類:

static class SingletonTest extends SingletonBase{ 
     public static SingletonTest getInstance(){ 
      return getInstance(SingletonTest.class); 
     } 
    } 

還有建議使用ENUM的做法,很容易實現,但breakes打開關閉原則來自SOLID

+0

這個工作的唯一原因是因爲所有的類都是兄弟嵌套類,並且可以訪問私有構造函數。不知道你爲什麼沒有在代碼片段中顯示它。這打破了你提到的原則,遠遠超過枚舉。你基本上實現了自己與使用枚舉免費獲得的相同的東西。 – Oleg

+0

@Oleg好吧,你可以將構造函數改爲受保護的,它仍然可以作爲單例基礎 – mlecz