这是我的示例抽象单例类:
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来运行这些测试。
摘要单身人士?对我来说听起来不太可行。 Singleton模式需要一个private
构造函数,这已经使子类化成为不可能。你需要重新考虑你的设计。 Abstract Factory pattern可能更适合特定目的。
你试图让一个抽象类扮演两个截然不同的角色:
最重要的是,你还希望服务是单例并在整个类系列上强制执行'singletoness',因为某些原因你不考虑缓存服务实例。
有人(我会)会说它闻起来非常糟糕,因为多种原因它违反了关注点,单身人士无法进行单元测试“等等。
其他人会说它没关系,它不需要很多不同的基础设施,并且在一些非常常见的第三方(传统)Java API中有一些流畅的界面。
不好的部分要求孩子们选择父工厂方法返回的实现。这个责任应该被推高并集中到抽象的超类中。否则,您将在非常不同的上下文中使用的模式混合在一起,抽象工厂(父级决定客户将要获得的类家族)和工厂方法(子工厂选择客户端将获得的内容)。
Factory Method实际上也不可能,因为您不能覆盖静态方法或构造函数。
有一些(丑陋的)方法来实现你的目标:
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
父级也可以使用反射,缓存实例等。
更加友好的测试和扩展解决方案只是标准的关注点分离。这些孩子本身不再是单身,但你将它们打包成一些内部包,你将其记录为“私有”,外部包中的公共抽象父将处理子实例的缓存或池化,强制执行任何实例化这些课程需要政策。
A.getInstance()
永远不会调用派生实例,因为它是静态绑定的。
我将对象的创建与实际对象本身分开,并创建一个返回特定类类型的适当的factory。目前尚不清楚你如何参数化,给定你的示例代码 - 是通过一些参数进行参数化,还是类选择是静态的?
你可能想重新考虑单身,顺便说一句。这是一个常见的反模式,并使测试(特别是)成为一种痛苦,因为测试中的类将提供他们自己的该类实例作为单例。您不能提供虚拟实现,也不能(轻松地)为每个测试创建新实例。
单身人士有点像yucky。摘要坚持继承,如果可能的话,你常常想要avoid。总的来说,我想重新考虑你要做的是simplest possible way,如果是这样,那么一定要使用工厂而不是单身人士(单身人士很难在unit tests中替代,而工厂可以被告知轻松替换测试实例)。
一旦你开始考虑将它作为一个工厂实现,抽象的东西就会自行解决(要么它显然是必要的,要么可以很容易地代替接口)。
除了其他人指出的问题之外,在instance
中使用A
字段意味着在整个VM中只能有一个单例。如果你还有:
public class C extends A {
private C() { }
static {
instance = new C();
}
//...implementations of my abstract methods...
}
...然后最后加载的B
或C
中的任何一个都将赢,而另一个的单例实例将丢失。
这只是做事的坏方法。
我发现了在抽象类中使用Singleton的更好方法,它使用静态Map来维护子类的实例。
public abstract class AbstractSingleton {
private static Map<String, AbstractSingleton> registryMap = new HashMap<String, AbstractSingleton>();
AbstractSingleton() throws SingletonException {
String clazzName = this.getClass().getName();
if (registryMap.containsKey(clazzName)) {
throw new SingletonException("Cannot construct instance for class " + clazzName + ", since an instance already exists!");
} else {
synchronized (registryMap) {
if (registryMap.containsKey(clazzName)) {
throw new SingletonException("Cannot construct instance for class " + clazzName + ", since an instance already exists!");
} else {
registryMap.put(clazzName, this);
}
}
}
}
@SuppressWarnings("unchecked")
public static <T extends AbstractSingleton> T getInstance(final Class<T> clazz) throws InstantiationException, IllegalAccessException {
String clazzName = clazz.getName();
if (!registryMap.containsKey(clazzName)) {
synchronized (registryMap) {
if (!registryMap.containsKey(clazzName)) {
T instance = clazz.newInstance();
return instance;
}
}
}
return (T) registryMap.get(clazzName);
}
public static AbstractSingleton getInstance(final String clazzName)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
if (!registryMap.containsKey(clazzName)) {
Class<? extends AbstractSingleton> clazz = Class.forName(clazzName).asSubclass(AbstractSingleton.class);
synchronized (registryMap) {
if (!registryMap.containsKey(clazzName)) {
AbstractSingleton instance = clazz.newInstance();
return instance;
}
}
}
return registryMap.get(clazzName);
}
@SuppressWarnings("unchecked")
public static <T extends AbstractSingleton> T getInstance(final Class<T> clazz, Class<?>[] parameterTypes, Object[] initargs)
throws SecurityException, NoSuchMethodException, IllegalArgumentException,
InvocationTargetException, InstantiationException, IllegalAccessException {
String clazzName = clazz.getName();
if (!registryMap.containsKey(clazzName)) {
synchronized (registryMap) {
if (!registryMap.containsKey(clazzName)) {
Constructor<T> constructor = clazz.getConstructor(parameterTypes);
T instance = constructor.newInstance(initargs);
return instance;
}
}
}
return (T) registryMap.get(clazzName);
}
static class SingletonException extends Exception {
private static final long serialVersionUID = -8633183690442262445L;
private SingletonException(String message) {
super(message);
}
}
}