簡體   English   中英

如何在 Java 中實現可子類化的單例

[英]How to implement a subclassable Singleton in Java

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

我相當確定用工廠實現這將非常簡單,但我很想知道它是否可以在不知道所有子類類型的情況下完成,即通用的單例執行器類。

現在我主要只是在玩弄這樣的想法,所以我不是在這里尋找對設計選擇提出質疑的反饋。

我工作的語言是Java,但現在我不一定擔心實現細節,除非在Java中不可能,然后,當然,提供證據證明它是不可能的。

我想知道你想做什么。 腦海中浮現出幾種可能性,知道這將走向何方可能會有所幫助。

選項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,因為您不會遇到運行時故障。

首先,通用單例沒有意義。
父類不應該負責檢索和管理其子類的實例。
它以兩種方式(父-> 子和子-> 父)創建強耦合。

其次,正如 shmosel 所說,不可能對單例(沒有特殊工件)進行子類化。
單例模式的關鍵是缺乏在單例類之外實例化類的能力,因此不必提供公共構造函數。
在這些情況下,如何對單例類進行子類化?

要允許對單例類進行子類化,您必須有一個公共構造函數,同時確保該類的實例不超過一個。
反轉控制容器如 Spring 可能會這樣做(這是一個特殊工件的例子)。


作為旁注,我不考慮對訪問修飾符進行調整,例如可以允許對單例進行子類化的package-private修飾符,但它的限制是單例只能是包外的單例。

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

  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 方法,它很容易實現,但打破了 SOLID 的開閉原則

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM