【问题标题】:Singleton with subclassing in javajava中带有子类化的单例
【发布时间】:2011-12-18 04:31:12
【问题描述】:

在 java 中实现单例的最常见方法是使用私有构造函数和以下形式的公共访问器方法 --

public class Singleton {
    private static Singleton instance = null;
    private Singleton() {

    }
    public static synchronized Singleton getInstance(){
        if (instance == null) {
        instance = new Singleton();         
        } 
        return instance;
    }
}

但是,由于构造函数是私有的,它会阻止对单例进行子类化。有什么方法可以让我们创建一个允许子类化的单例?

【问题讨论】:

  • 我认为最常见的实现单例的方法是对一个实例使用枚举。
  • 这可能是一种“更好”的方式,但绝对不是最常见的方式。在我从事的所有项目中,Singleton 的实现方式如上所示。另外,查看网上关于 Singleton 的任何文章,这是我发现的。

标签: java design-patterns singleton


【解决方案1】:

当您拥有class A extends B 时,A 的实例本质上“包含”B 的实例。所以继承的概念本身就与单例模型相反。

根据您的需要,我会考虑使用组合/委托。 (A 将引用单例,而不是扩展其类)。如果您出于某种原因需要继承,请使用 Singleton 方法创建一个接口,让 Singleton 实现该接口,然后让另一个类也实现该接口,并委托给 Singleton 以实现相关方法。

【讨论】:

  • 你能解释一下这个“与单例相反”的说法吗?扩展它并没有真正破坏任何东西,因为您基本上是在让一个类重用单例的功能,对吧?
  • @Boon 当人们谈论单例时,他们可能意味着只有一个对象instanceof TheSingleton 为真。这不适用于继承,因为A 的任何实例也是B 的实例。
  • @yshavit 如果我错了,请纠正我。我认为您建议使用装饰器模式,对吗?
【解决方案2】:

如果你可以继承它,它就不是一个真正的单例,因为每个继承的类都至少有一个实例。

但是,您可以只创建构造函数protected

【讨论】:

  • 在 90% 的情况下,一个方法被标记为私有,它确实应该受到保护。
  • @jiggy:为继承而设计的问题在于,它几乎使任何更改都成为破坏性更改。继承不是为继承而设计的第三方类可能会引入各种微妙的问题(尤其是重入)并使代码非常脆弱。
  • 谢谢。制作单例类protected的构造函数的目的是什么?将其设为构造函数protected 不会阻止单例类拥有子类,是吗?
  • @Tim 在典型的单例设计中,您可以将构造函数设为private,这样使用单例的类就不能创建多个实例。但是,这意味着您不能将其子类化,因为您无法从子类访问构造函数。
  • 但是使构造函数protected 也将它打开到同一个包中的其他类。
【解决方案3】:

我恭敬地提供与建议单例不应该被子类化的 cmets 的对立面。 Gamma、Helm、Johnson 和 Vlissides 的“Design Patterns: Elements of Reusable Object-Oriented Software”(又名“The Gang of Four”一书或简称“GOF”)中讨论了单例的子类化。

一个正确子类化的单例也将是一个单例。例如,假设您有一个单例来处理名为 Logger 的记录信息性消息。现在假设您想扩展 Logger 的功能以使用 HTML 标记写入输出。我们称它为 HTMLLogger。这两个类都是单例类,但其中一个扩展了另一个的功能。

首先,这是一个简单的单例及其测试用例:

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.simple;

public class SimpleSingleton {
    // The instance - only one of these can exist in the system (currently not accounting for threads).
    private static SimpleSingleton instance;

    private int sampleValue;

    public static SimpleSingleton getInstance() {
        if (instance == null) {
            instance = new SimpleSingleton();               
        }
        return instance;
    }

    public int getSampleValue() {
        return sampleValue;
    }

    public void setSampleValue(int sampleValue) {
        this.sampleValue = sampleValue;
    }

    protected SimpleSingleton() {
        // Insures construction cannot occur outside of class.
    }
}

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.simple.test;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

import study.design.patterns.creational.singleton.simple.SimpleSingleton;

public class SimpleSingletonTest {

    @Test
    public void testIllegalCreation() {
        // The following line will not compile because the constructor is not visible.
        // Singleton instance = new Singleton();        
    }

    @Test
    public void testObjectEquality() {
        SimpleSingleton instance1 = SimpleSingleton.getInstance();
        assertNotNull(instance1);
        SimpleSingleton instance2 = SimpleSingleton.getInstance();
        assertNotNull(instance2);
        assertEquals(instance1, instance2);     
    }

    @Test
    public void testDataEquality() {
        SimpleSingleton instance1 = SimpleSingleton.getInstance();
        assertNotNull(instance1);
        SimpleSingleton instance2 = SimpleSingleton.getInstance();
        assertNotNull(instance2);
        assertEquals(instance1, instance2);
        instance1.setSampleValue(5);
        int testSampleValue = instance2.getSampleValue();
        assertEquals(testSampleValue, 5);
    }
}

/////////////////////////////////////////////////////////////////////////////

我发现了四种方法可以将单例子类化。

选项 1. 蛮力。

本质上,子类重新实现了使类成为单例的关键特性。即静态实例变量、静态getInstance方法和隐藏构造函数。在这种情况下,隐藏的构造函数调用基类。

这是一个示例基类、子类和测试用例:

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassbruteforce;

// This singleton can be extended (subclassed)
public class BruteForceExtendableSingleton {
    // The instance - only one of these can exist in the system (currently not accounting for threads).
    private static BruteForceExtendableSingleton instance;

    private int sampleValue;

    public static BruteForceExtendableSingleton getInstance() {
        // The problem with this version of an extendable singleton is clear from the code below - every subclass possible is hard-coded.
        // Creating a new subclass requires modifying the base class as well, which violates the open-closed principle.
        if (instance == null) {
            instance = new BruteForceExtendableSingleton();
        }
        return instance;
    }

    public int getSampleValue() {
        return sampleValue;
    }

    public void setSampleValue(int sampleValue) {
        this.sampleValue = sampleValue;
    }

    protected BruteForceExtendableSingleton() {
        // Insures construction cannot occur outside of class.
    }
}

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassbruteforce;

public class BruteForceSubclassSingleton extends BruteForceExtendableSingleton {
    // The instance - only one of these can exist in the system (currently not accounting for threads).
    private static BruteForceSubclassSingleton instance;

    private int sampleValue2;

    public static BruteForceSubclassSingleton getInstance() {
        // The problem with this version of an extendable singleton is clear from the code below - every subclass possible is hard-coded.
        // Creating a new subclass requires modifying the base class as well, which violates the open-closed principle.
        if (instance == null) {
            instance = new BruteForceSubclassSingleton();
        }
        return instance;
    }

    public int getSampleValue2() {
        return sampleValue2;
    }

    public void setSampleValue2(int sampleValue2) {
        this.sampleValue2 = sampleValue2;
    }

    protected BruteForceSubclassSingleton() {
        super();
    }
}

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassbruteforce.test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

import study.design.patterns.creational.singleton.subclassbruteforce.BruteForceExtendableSingleton;
import study.design.patterns.creational.singleton.subclassbruteforce.BruteForceSubclassSingleton;

public class BruteForceExtendableSingletonTest {

    @Test
    public void testIllegalCreation() {
        // The following lines will not compile because the constructor is not visible.
        // BruteForceExtendableSingleton instance = new BruteForceExtendableSingleton();        
        // BruteForceSubclassSingleton instance2 = new BruteForceSubclassSingleton();
    }

    @Test
    public void testCreateBruteForceExtendableSingleton() {
        BruteForceExtendableSingleton singleton = BruteForceExtendableSingleton.getInstance();
        assertNotNull(singleton);
        // Check that the singleton is an ExtendableSingleton, but not a FixedSubclassSingleton. 
        assertTrue(singleton instanceof BruteForceExtendableSingleton);
        assertFalse(singleton instanceof BruteForceSubclassSingleton);      
    }

    @Test
    public void testCreateBruteForceSubclassSingleton() {
        BruteForceExtendableSingleton singleton = BruteForceSubclassSingleton.getInstance();
        assertNotNull(singleton);
        // Check that the singleton is a BruteForceSubclassSingleton. 
        assertTrue(singleton instanceof BruteForceSubclassSingleton);
    }

    @Test
    public void testCreateBothBruteForceSingletons() {
        BruteForceExtendableSingleton singleton = BruteForceExtendableSingleton.getInstance();
        assertNotNull(singleton);
        assertTrue(singleton instanceof BruteForceExtendableSingleton);
        assertFalse(singleton instanceof BruteForceSubclassSingleton);

        BruteForceExtendableSingleton singleton2 = BruteForceSubclassSingleton.getInstance();
        assertNotNull(singleton2);
        assertTrue(singleton2 instanceof BruteForceSubclassSingleton);

        assertFalse(singleton == singleton2);
    }   
}

/////////////////////////////////////////////////////////////////////////////

优点: 允许两个单例同时存在。

缺点: 重复创建单例的工作。子类的单例性质并非来自其基类。

如果单例需要分开,可能需要更好的设计来共享其他方法而不是子类化。

选项 2。从一组固定的类中进行选择。

在这种情况下,基类中的 getInstance 方法根据标志(例如系统属性)确定要使用的实例。在代码示例中,我使用了类本身的名称。使用一系列 if 块,代码决定如何初始化实例。

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassfixed;

// This singleton can be extended (subclassed)
public class FixedExtendableSingleton {
    // The instance - only one of these can exist in the system (currently not accounting for threads).
    private static FixedExtendableSingleton instance;

    private int sampleValue;

    public static FixedExtendableSingleton getInstance() {
        // The problem with this version of an extendable singleton is clear from the code below - every subclass possible is hard-coded.
        // Creating a new subclass requires modifying the base class as well, which violates the open-closed principle.
        if (instance == null) {
            String singletonName = System.getProperty("study.design.patterns.creational.singleton.classname");
            if (singletonName.equals(FixedExtendableSingleton.class.getSimpleName())) {
                instance = new FixedExtendableSingleton();
            } else if (singletonName.equals(FixedSubclassSingleton.class.getSimpleName())) {
                instance = new FixedSubclassSingleton();                
            }
        }
        return instance;
    }

    public static void clearInstance() {
        // This method wipes out the singleton. 
        // This is purely for testing purposes so getInstance can reconnect to a new singleton if needed.
        instance = null;
    }

    public int getSampleValue() {
        return sampleValue;
    }

    public void setSampleValue(int sampleValue) {
        this.sampleValue = sampleValue;
    }

    protected FixedExtendableSingleton() {
        // Insures construction cannot occur outside of class.
    }
}

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassfixed;

public class FixedSubclassSingleton extends FixedExtendableSingleton {

    private int sampleValue2;

    public int getSampleValue2() {
        return sampleValue2;
    }

    public void setSampleValue2(int sampleValue2) {
        this.sampleValue2 = sampleValue2;
    }

    // Must be defined to prevent creation of a public default constructor. 
    protected FixedSubclassSingleton() {
        super();
    }
}

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassfixed.test;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import study.design.patterns.creational.singleton.subclassfixed.FixedExtendableSingleton;
import study.design.patterns.creational.singleton.subclassfixed.FixedSubclassSingleton;

public class FixedExtendableSingletonTest {

    @Test
    public void testIllegalCreation() {
        // The following lines will not compile because the constructor is not visible.
        // ExtendableSingleton instance = new ExtendableSingleton();        
        // FixedSubclassSingleton instance = new FixedSubclassSingleton();
    }

    @Test
    public void testCreateExtendableSingleton() {
        System.setProperty("study.design.patterns.creational.singleton.classname", "FixedExtendableSingleton");
        FixedExtendableSingleton singleton = FixedExtendableSingleton.getInstance();
        assertNotNull(singleton);
        // Check that the singleton is an ExtendableSingleton, but not a FixedSubclassSingleton. 
        assertTrue(singleton instanceof FixedExtendableSingleton);
        assertFalse(singleton instanceof FixedSubclassSingleton);

    }

    @Test
    public void testCreateFixedSubclassSingleton() {
        System.setProperty("study.design.patterns.creational.singleton.classname", "FixedSubclassSingleton");
        FixedExtendableSingleton singleton = FixedExtendableSingleton.getInstance();
        assertNotNull(singleton);
        // Check that the singleton is a FixedSubclassSingleton. 
        assertTrue(singleton instanceof FixedSubclassSingleton);
    }

    @AfterEach
    protected void tearDown() {
        FixedExtendableSingleton.clearInstance();
    }   
}

/////////////////////////////////////////////////////////////////////////////

优点: 子类与单例行为的更清晰绑定。 减少重复代码。

缺点: 只定义了一组固定的子类。添加新的子类需要修改 getInstance 方法。

选项 3。从一组动态类中确定要使用的单例。

此方法试图消除为每个子类修改 getInstance 的需要。这个想法是在基类中包含一个名称到单例的注册表(映射),并在 getInstance 中查找正确的名称。

为了使用单例填充注册表,需要预先创建每个单例。这是怎么做到的?根据 GOF,我们可以将静态变量分配给对象的实例。加载类时,构造单例,构造函数将对象添加到注册表中。这更复杂,但它有效(有点)。

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassflexible;

import java.util.HashMap;
import java.util.Map;

//This singleton can be extended (subclassed)
public class FlexibleExtendableSingleton {
    // The instance - only one of these can exist in the system (currently not accounting for threads).
    private static FlexibleExtendableSingleton instance;

    // This must appear before thisSingleton, because the constructor requires the registry.
    protected static Map<String, FlexibleExtendableSingleton> registry = new HashMap<String, FlexibleExtendableSingleton>();

    // This singleton - each class in the hierarchy needs one of these. It will trigger construction (and therefore, registration).
    private static FlexibleExtendableSingleton thisSingleton = new FlexibleExtendableSingleton();

    public static void activateClass() {
        // Do nothing special.
    }

    private int sampleValue;

    protected static void register(String name, FlexibleExtendableSingleton singletonClass) {
        registry.put(name, singletonClass); 
    }

    protected static FlexibleExtendableSingleton lookupFromRegistry(String name) {
        return registry.get(name);
    }

    public static FlexibleExtendableSingleton getInstance() {
        if (instance == null) {
            String singletonName = System.getProperty("study.design.patterns.creational.singleton.classname");
            instance = lookupFromRegistry(singletonName);
        }
        return instance;
    }

    public static void clearInstance() {
        // This method wipes out the singleton. 
        // This is purely for testing purposes so getInstance can reconnect to a new singleton if needed.
        instance = null;
    }

    public int getSampleValue() {
        return sampleValue;
    }

    public void setSampleValue(int sampleValue) {
        this.sampleValue = sampleValue;
    }

    protected FlexibleExtendableSingleton() {
        // Protected insures construction cannot occur outside of class.

        // Register the class when it is constructed by its static method.
        // Subclasses will be able to use this method as well.
        register(this.getClass().getSimpleName(), this);
    }

}

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassflexible;

import study.design.patterns.creational.singleton.subclassdynamicload.DynamicLoadExtendableSingleton;

public class FlexibleSubclassSingleton extends FlexibleExtendableSingleton {
    // This singleton - each class in the hierarchy needs one of these. It will trigger construction (and therefore, registration).
    private static FlexibleSubclassSingleton thisSingleton = new FlexibleSubclassSingleton();

    private int sampleValue2;

    public static void activateClass() {
        // Do nothing special.
    }

    public int getSampleValue2() {
        return sampleValue2;
    }

    public void setSampleValue2(int sampleValue2) {
        this.sampleValue2 = sampleValue2;
    }

    // Must be defined to prevent creation of a public default constructor. 
    protected FlexibleSubclassSingleton() {
        // The following line will also register the class.
        super();
    }
}

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassflexible.test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import study.design.patterns.creational.singleton.subclassflexible.FlexibleExtendableSingleton;
import study.design.patterns.creational.singleton.subclassflexible.FlexibleSubclassSingleton;

public class FlexibleExtendableSingletonTest {

    @Test
    public void testIllegalCreation() {
        // The following lines will not compile because the constructor is not visible.
        // FlexibleExtendableSingleton instance = new FlexibleExtendableSingleton();        
        // FlexibleSubclassSingleton instance2 = new FlexibleSubclassSingleton();
    }

    @Test
    public void testCreateFlexibleExtendableSingleton() {
        System.setProperty("study.design.patterns.creational.singleton.classname", "FlexibleExtendableSingleton");
        FlexibleExtendableSingleton.activateClass();
        FlexibleSubclassSingleton.activateClass();      
        FlexibleExtendableSingleton singleton = FlexibleExtendableSingleton.getInstance();
        assertNotNull(singleton);
        // Check that the singleton is an ExtendableSingleton, but not a FixedSubclassSingleton. 
        assertTrue(singleton instanceof FlexibleExtendableSingleton);
        assertFalse(singleton instanceof FlexibleSubclassSingleton);        
    }

    @Test
    public void testCreateFlexibleSubclassSingleton() {
        System.setProperty("study.design.patterns.creational.singleton.classname", "FlexibleSubclassSingleton");
        FlexibleExtendableSingleton.activateClass();
        FlexibleSubclassSingleton.activateClass();      
        FlexibleExtendableSingleton singleton = FlexibleExtendableSingleton.getInstance();
        assertNotNull(singleton);
        // Check that the singleton is a FlexibleSubclassSingleton. 
        assertTrue(singleton instanceof FlexibleSubclassSingleton);
    }

    @AfterEach
    protected void tearDown() {
        FlexibleExtendableSingleton.clearInstance();
    }   
}

/////////////////////////////////////////////////////////////////////////////

注意每个单例中的方法“activateClass”。这个方法是空的,似乎什么都不做。实际上,它是第一次触发加载类。调用时,加载类,创建静态单例实例,将条目添加到注册表。如果类没有被加载,注册表将不会被填充,getInstance 会为除基类之外的任何类返回 null,因为调用 getInstance 也会触发加载基类。

或者,您可以使用 ClassLoader 来加载所有单例类,而不是使用“activateClass”方法。您仍然需要显式加载每个单例类。

优点: getInstance 不必每次都修改。

缺点: 每个子类都需要一个空的 activateClass 方法(或加载类的其他方式),该方法必须在 getInstance 之前调用。由于每个单例类都必须被激活,我们并没有从选项 2 中获得太多改进。

选项 4. 按名称动态加载单例。

在上面的选项 3 中,我们遇到了加载单例类以填充注册表的问题。既然单例的选择已经由系统属性控制,为什么不直接加载要使用的单例类并将 that 设置为实例呢?

使用反射,我们可以按名称加载类,定位静态单例(“thisSingleton”字段),并将其分配给实例。

注意:反射使开发人员能够绕过封装,因此应谨慎使用。在这种情况下,它的使用仅限于 getInstance。

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassdynamicload;

import java.lang.reflect.Field;

//This singleton can be extended (subclassed)
public class DynamicLoadExtendableSingleton {
    // The instance - only one of these can exist in the system (currently not accounting for threads).
    private static DynamicLoadExtendableSingleton instance;

    // This singleton - each class in the hierarchy needs one of these. It will trigger construction (and therefore, registration).
    private static DynamicLoadExtendableSingleton thisSingleton = new DynamicLoadExtendableSingleton();

    private int sampleValue;

    public static DynamicLoadExtendableSingleton getInstance() {
        if (instance == null) {
            String singletonName = System.getProperty("study.design.patterns.creational.singleton.classname");

            ClassLoader loader = DynamicLoadExtendableSingleton.class.getClassLoader();
            try {
                Class<?> singletonClass = loader.loadClass(singletonName);
                Field field = singletonClass.getDeclaredField("thisSingleton");
                field.setAccessible(true);
                instance = (DynamicLoadExtendableSingleton) field.get(null);
            } catch (ClassNotFoundException e) {
                // The class was not found.
                // TODO: Add error handling code here.
            } catch (NoSuchFieldException e) {
                // The field does not exist - fix the singleton class to include thisSingleton field.
                // TODO: Add error handling code here.
            } catch (IllegalAccessException e) {
                // Should not occur - we make the field accessible just for this purpose.
                // TODO: Add error handling code here.
            }
        }
        return instance;
    }

    public static void clearInstance() {
        // This method wipes out the singleton. 
        // This is purely for testing purposes so getInstance can reconnect to a new singleton if needed.
        instance = null;
    }

    public int getSampleValue() {
        return sampleValue;
    }

    public void setSampleValue(int sampleValue) {
        this.sampleValue = sampleValue;
    }

    protected DynamicLoadExtendableSingleton() {
        // Protected insures construction cannot occur outside of class.
    }

}

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassdynamicload;

public class DynamicLoadSubclassSingleton extends DynamicLoadExtendableSingleton {
    // This singleton - each class in the hierarchy needs one of these. It will trigger construction (and therefore, registration).
    private static DynamicLoadSubclassSingleton thisSingleton = new DynamicLoadSubclassSingleton();

    private int sampleValue2;

    public int getSampleValue2() {
        return sampleValue2;
    }

    public void setSampleValue2(int sampleValue2) {
        this.sampleValue2 = sampleValue2;
    }

    // Must be defined to prevent creation of a public default constructor. 
    protected DynamicLoadSubclassSingleton() {
        super();
    }
}

/////////////////////////////////////////////////////////////////////////////

package study.design.patterns.creational.singleton.subclassdynamicload.test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import study.design.patterns.creational.singleton.subclassdynamicload.DynamicLoadExtendableSingleton;
import study.design.patterns.creational.singleton.subclassdynamicload.DynamicLoadSubclassSingleton;

public class DynamicLoadExtendableSingletonTest {

    @Test
    public void testIllegalCreation() {
        // The following lines will not compile because the constructor is not visible.
        // DynamicLoadExtendableSingleton instance = new DynamicLoadExtendableSingleton();      
        // DynamicLoadSubclassSingleton instance2 = new DynamicLoadSubclassSingleton();
    }

    @Test
    public void testCreateDynamicLoadExtendableSingleton() {
        System.setProperty("study.design.patterns.creational.singleton.classname", DynamicLoadExtendableSingleton.class.getName());
        DynamicLoadExtendableSingleton singleton = DynamicLoadExtendableSingleton.getInstance();
        assertNotNull(singleton);
        // Check that the singleton is an ExtendableSingleton, but not a FixedSubclassSingleton. 
        assertTrue(singleton instanceof DynamicLoadExtendableSingleton);
        assertFalse(singleton instanceof DynamicLoadSubclassSingleton);     
    }

    @Test
    public void testCreateDynamicLoadSubclassSingleton() {
        System.setProperty("study.design.patterns.creational.singleton.classname", DynamicLoadSubclassSingleton.class.getName());
        DynamicLoadExtendableSingleton singleton = DynamicLoadExtendableSingleton.getInstance();
        assertNotNull(singleton);
        // Check that the singleton is a DynamicLoadSubclassSingleton. 
        assertTrue(singleton instanceof DynamicLoadSubclassSingleton);
    }

    @AfterEach
    protected void tearDown() {
        DynamicLoadExtendableSingleton.clearInstance();
    }   
}

/////////////////////////////////////////////////////////////////////////////

优点: 子类不需要激活类的方法。唯一需要的代码是“thisSingleton”字段。 getInstance 方法不需要对每个新的子类进行修改。

缺点: 反射可能会更慢,但由于它只在一个地方使用并且仅在分配单例时使用,因此风险很小。 如果类名不正确,可能会出错。同样,这是最小的风险。

总之,虽然子类化单例可能并不常见,但 GOF 书中尽可能地解决了它。有几种方法可以支持子类化单例,每种方法都有优点和缺点。上面列出的一些方法直接来自本书。使用反射的方法是我的补充。

【讨论】:

    【解决方案4】:

    您是否希望为许多单身人士提供一些可继承的行为?如果是这样,也许您可​​以将该代码移动到抽象类中。

    正如 SLaks 所指出的,扩展单例将不再使其成为单例模式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-05
      • 1970-01-01
      • 1970-01-01
      • 2016-12-24
      • 1970-01-01
      相关资源
      最近更新 更多