以下是我的示例抽象单例类:
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来运行这些测试。
答案 0 :(得分:19)
摘要单身人士?对我来说听起来不太可行。 Singleton模式需要private
构造函数,这已经使子类化成为不可能。你需要重新考虑你的设计。 Abstract Factory pattern可能更适合特定目的。
答案 1 :(得分:8)
从您的帖子开始,即使没有明确说明,听起来您希望抽象类扮演两个不同的角色。 角色是:
此外,您希望服务在整个类系列中单独执行'singletoness',由于某种原因,您不足以缓存服务实例。
这很好。
有人会说它闻起来非常糟糕,因为“违反了关注点的分离”,“单身人士和单位测试并不能很好地结合在一起”。
其他人会说这是好的,因为你赋予了在家庭中实例化正确孩子的责任,并且整体上也暴露了更流畅的界面,因为你不需要调解一个除暴露静态之外什么都不做的工厂方法
在您希望子项负责选择父工厂方法返回的实现之后,出现了什么问题。 这在设计方面是错误的,因为你委托给所有孩子,可以简单地将其推入并集中到抽象超类中,并且它还表明你正在混合使用在不同上下文中使用的模式,抽象工厂(父级决定什么类的类)客户将获得)和工厂方法(儿童工厂选择客户将获得的)。
工厂方法不仅不是必需的,而且也不可能使用工厂方法,因为它集中于实现或覆盖“实例”方法。静态方法和构造函数都没有覆盖。
回过头来看一个抽象单例的最初好或坏的想法,选择要暴露的行为,有几种方法可以解决最初的问题, 一个可能是以下,看起来很糟糕但我猜你接近你想要的东西:
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
父母也可以使用反射。
更多测试友好和扩展友好的解决方案只是让孩子不是单身,而是打包成一些内部包,你将其记录为“私人”和抽象的父,可以暴露“单身模仿”静态getInstance()和将缓存子实例,强制客户端始终获得相同的服务实例。
答案 2 :(得分:4)
A.getInstance()
永远不会调用派生实例,因为它是静态绑定的。
我将对象的创建与实际对象本身分开,并创建一个返回特定类类型的适当factory。鉴于您的示例代码 - 是通过某个参数进行参数化还是类选择是静态的,您不清楚如何参数化?
你可能想重新考虑单身,顺便说一句。这是一个常见的反模式并使测试(特别是)变得很痛苦,因为测试中的类将提供他们自己的该类实例作为单例。您不能提供虚拟实现,也不能(轻松地)为每个测试创建新实例。
答案 3 :(得分:4)
单身人士有点yucky。摘要坚持继承,如果可能的话,你常常想要avoid。总的来说,我会重新考虑你要做的是simplest possible way,如果是的话,那么一定要使用工厂而不是单身人士(单身人士在unit tests而工厂很难替代可以告诉他们轻松替换测试实例。
一旦你开始考虑将它作为工厂实现,抽象的东西就会自行排序(要么显然是必要的,要么可以轻易地代替接口)。
答案 4 :(得分:3)
除了其他人指出的问题之外,在instance
中使用A
字段意味着您在整个VM中只能拥有一个单例。如果您还有:
public class C extends A {
private C() { }
static {
instance = new C();
}
//...implementations of my abstract methods...
}
...然后,最后加载B
或C
中的任何一个都将获胜,而另一个的单例实例将会丢失。
这只是做事的坏方法。
答案 5 :(得分:1)
我发现了在抽象类中使用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);
}
}
}