在java中实现单例的最常用方法是使用私有构造函数和表单的公共访问器方法 -
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static synchronized Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
但是,由于构造函数是私有的,因此它会阻止对单例进行子类化。有没有什么方法可以让我们创建一个允许子类化的单例?
答案 0 :(得分:10)
如果你可以继承它,它实际上不是一个单例,因为每个继承的类至少有一个实例。
但是,您可以创建构造函数protected
。
答案 1 :(得分:9)
当您拥有class A extends B
时,A
的实例基本上包含“B
的实例。因此,继承的概念与单身模型相反。
根据您的需要,我会考虑使用合成/委托。 (A会引用单例,而不是扩展它的类)。如果由于某种原因需要继承,请使用Singleton方法创建接口,让Singleton实现该接口,然后让另一个类也实现该接口,并委托单例执行相关方法。
答案 2 :(得分:1)
我谨对建议不要将单身人士归为一类的意见提出反对。在Gamma,Helm,Johnson和Vlissides的“设计模式:可重用的面向对象软件的元素”中讨论了单例的子类化(又名“四人帮”一书,简称“ 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书中将其视为可行。有几种支持单例子类化的方法,每种方法都有其优点和缺点。上面列出的一些方法直接来自本书。使用反射的方法是我的补充。
答案 3 :(得分:0)
您是否希望为多个单例提供一些可继承的行为?如果是这样,也许您可以将该代码移动到抽象类中。
正如SLaks指出的那样,扩展单身人士将不再使其成为单身人士模式。