我想实现一个通用的单例工厂模式,其中我将所需对象的类作为参数传递,并且工厂类应该检查映射中是否已经为其创建了一个对象,如果有,则从映射中返回该对象。如果没有,则创建一个新实例,将其放入映射中并返回该实例。
我可以将通用返回类型设置为 Object,但我不想在调用 get 实例方法的每个地方都强制转换返回的对象。
下面是代码:我在
c.cast(instance);
行收到编译错误
我们不使用 spring/依赖注入,而是尝试实现公共类来负责创建所有单例对象。
public class SingletonFactory {
public static Map<String,Object> objectFactory = new HashMap<String, Object>();
public static <T extends Object> T getInstance(Class<?> c){
String key = c.toString();
Object instance= objectFactory.get(key);
if (instance == null) {
synchronized (c) {
try {
instance = c.newInstance();
objectFactory.put(key, instance);
} catch(IllegalAccessException | InstantiationException e) {
throw new RuntimeException("Exception while creating singleton instance for class : "+key+" - Exception Message : "+e);
}
}
}
return c.cast(instance);
}
}
首先,我可以指出,
<T extends Object>
可以只用<T>
替换,因为Java中涉及泛型的所有内容都必须是一个对象。
您真正接近的第二部分是
Class<?> c
。这就是说,您可以传入任何类,它将返回 T
的任何类型。如果您认为看起来更好,可以将 c.cast(instance)
替换为 (T) instance
,但是,实际上存在差异,这里更详细地介绍:Java Class.cast() 与强制转换运算符 .
最终代码如下所示:
public class SingletonFactory {
public static Map<String,Object> objectFactory = new HashMap<String, Object>();
public static <T> T getInstance(Class<T> c){
synchronized (c) {
String key = c.toString();
Object instance= objectFactory.get(key);
if (instance == null) {
try {
instance = c.newInstance();
objectFactory.put(key, instance);
} catch(IllegalAccessException | InstantiationException e){
throw new RuntimeException("Exception while creating singleton instance for class : "+key, e);
}
}
return c.cast(instance);
// or
return (T) instance;
}
}
}
此外,如果您真的愿意,您可以保留原始代码中的所有内容,并在方法末尾将实例强制转换为
T
,它应该可以工作。唯一的事情是你的方法调用看起来像 SingletonFactory.getInstance<Foo>(Foo.class)
而不是 SingletonFactory.getInstance(Foo.class)
。这是因为原始代码中的 Class<?>
而不是 Class<T>
。
编辑:我还更改了代码以提前同步,谢谢@Khan9797
第二次编辑:使 RuntimeException 的实例化通过
e
作为原因。 (这是最好的做法,以防出现此异常,您需要完整的堆栈跟踪)
首先,你需要更早地同步,你应该简单地同步方法,否则,你可以在竞争条件下创建额外的实例。
其次,您应该像这样定义方法的泛型:
public static <T> T getInstance(Class<? extends T> c)
首先,
getInstance()
在创建新实例方面不是线程安全的。当多个线程同时运行并且 variable == null
是 true
时,您有可能创建给定类的多个实例。
public class SingletonFactory {
private static Map<Class, Object> objectHolder = new HashMap<>();
public <T> T getInstance(Class<T> clazz) {
Object instance = objectHolder.get(clazz);
if(instance == null) {
synchronized (clazz) {
if(instance == null) {
try{
instance = clazz.newInstance();
objectHolder.put(clazz, instance);
} catch (Exception e) {
// do some logging and maybe exit the program. Since the it would affect how whole system works.
}
}
}
}
return clazz.cast(instance);
}
}
但是更好的方法是使用急切初始化而不是惰性初始化。我们需要同步关键部分的原因是我们在需要时创建这些实例。所以这就成了一个
readers-writers
的问题。但如果我们只进行读取过程,那么我们不需要同步,因为我们不会修改它的值。如果您知道将要创建并需要访问的所有类,我们可以首先初始化它们。这样我们就可以摆脱 synchronized
性能缺陷
public class SingletonFactory {
private static Map<Class, Object> objectHolder = new HashMap<>();
private Map<Class, Object> initialize() {
Map<Class, Object> objectHolder = new HashMap<>();
// create some objects and put it into Map
return objectHolder;
}
public <T> T getInstance(Class<T> clazz) {
Object instance = objectHolder.get(clazz);
return clazz.cast(instance);
}
}