130 java oop singleton anti-patterns
我正在阅读维基百科上的Singleton文章,我遇到了这个例子:
public class Singleton {
// Private constructor prevents instantiation from other classes
private Singleton() {}
/**
* SingletonHolder is loaded on the first execution of Singleton.getInstance()
* or the first access to SingletonHolder.INSTANCE, not before.
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Run Code Online (Sandbox Code Playgroud)
虽然我非常喜欢这个Singleton的行为方式,但我看不出如何调整它以将参数合并到构造函数中.在Java中执行此操作的首选方法是什么?我必须这样做吗?
public class Singleton
{
private static Singleton singleton = null;
private final int x;
private Singleton(int x) {
this.x = x;
}
public synchronized static Singleton getInstance(int x) {
if(singleton == null) singleton = new Singleton(x);
return singleton;
}
}
Run Code Online (Sandbox Code Playgroud)
谢谢!
编辑:我想我已经开始引发争议,因为我希望使用Singleton.让我解释一下我的动机,希望有人能提出更好的想法.我正在使用网格计算框架来并行执行任务.一般来说,我有这样的事情:
// AbstractTask implements Serializable
public class Task extends AbstractTask
{
private final ReferenceToReallyBigObject object;
public Task(ReferenceToReallyBigObject object)
{
this.object = object;
}
public void run()
{
// Do some stuff with the object (which is immutable).
}
}
Run Code Online (Sandbox Code Playgroud)
会发生的是,即使我只是将对数据的引用传递给所有任务,但当任务被序列化时,数据会一遍又一遍地被复制.我想要做的是在所有任务中共享对象.当然,我可能会像这样修改类:
// AbstractTask implements Serializable
public class Task extends AbstractTask
{
private static ReferenceToReallyBigObject object = null;
private final String filePath;
public Task(String filePath)
{
this.filePath = filePath;
}
public void run()
{
synchronized(this)
{
if(object == null)
{
ObjectReader reader = new ObjectReader(filePath);
object = reader.read();
}
}
// Do some stuff with the object (which is immutable).
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,即使在这里,我遇到的问题是传递不同的文件路径在第一个传递之后没有任何意义.这就是为什么我喜欢在答案中发布的商店的想法.无论如何,我想将这个逻辑抽象为Singleton类,而不是包含在run方法中加载文件的逻辑.我不会提供另一个例子,但我希望你能得到这个想法.请让我听听你的想法,以更优雅的方式来完成我想要做的事情.再次感谢你!
Yuv*_*dam 161
我会明确指出:带参数的单例不是单例.
根据定义,单例是一个您想要实例化的对象,不超过一次.如果您尝试将参数提供给构造函数,那么单例的重点是什么?
你有两个选择.如果您希望使用某些数据初始化单例,可以在实例化后使用数据加载它,如下所示:
SingletonObj singleton = SingletonObj.getInstance();
singleton.init(paramA, paramB); // init the object with data
Run Code Online (Sandbox Code Playgroud)
如果你的单例正在执行的操作是重复的,并且每次都有不同的参数,你也可以将参数传递给正在执行的main方法:
SingletonObj singleton = SingletonObj.getInstance();
singleton.doSomething(paramA, paramB); // pass parameters on execution
Run Code Online (Sandbox Code Playgroud)
在任何情况下,实例化将始终是无参数的.否则你的单身人士不是单身人士.
aka*_*okd 39
我认为你需要像工厂这样的东西来拥有实例化和重用各种参数的对象.它可以通过使用synchronized HashMap
或ConcurrentHashMap
将参数(Integer
例如)映射到'singleton'可参数化类来实现.
虽然您可能会使用常规的非单例类(例如需要10.000不同的参数化单例).
以下是此类商店的示例:
public final class UsefulObjFactory {
private static Map<Integer, UsefulObj> store =
new HashMap<Integer, UsefulObj>();
public static final class UsefulObj {
private UsefulObj(int parameter) {
// init
}
public void someUsefulMethod() {
// some useful operation
}
}
public static UsefulObj get(int parameter) {
synchronized (store) {
UsefulObj result = store.get(parameter);
if (result == null) {
result = new UsefulObj(parameter);
store.put(parameter, result);
}
return result;
}
}
}
Run Code Online (Sandbox Code Playgroud)
为了进一步推动它,Java enum
也可以被认为(或用作参数化单例),尽管只允许固定数量的静态变体.
但是,如果您需要分布式1解决方案,请考虑一些横向缓存解决方案.例如:EHCache,Terracotta等.
1可能是在多台计算机上跨越多个VM.
mig*_*uel 12
您可以添加初始化方法,以便将实例化与获取分开.
public class Singleton {
private static Singleton singleton = null;
private final int x;
private Singleton(int x) {
this.x = x;
}
public static Singleton getInstance() {
if(singleton == null) {
throw new AssertionError("You have to call init first");
}
return singleton;
}
public synchronized static Singleton init(int x) {
if (singleton != null)
{
// in my opinion this is optional, but for the purists it ensures
// that you only ever get the same instance when you call getInstance
throw new AssertionError("You already initialized me");
}
singleton = new Singleton(x);
return singleton;
}
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以Singleton.init(123)
在应用启动中调用一次来配置它,例如.
ger*_*ico 11
如果要显示某些参数是必需的,也可以使用Builder模式.
public enum EnumSingleton {
INSTANCE;
private String name; // Mandatory
private Double age = null; // Not Mandatory
private void build(SingletonBuilder builder) {
this.name = builder.name;
this.age = builder.age;
}
// Static getter
public static EnumSingleton getSingleton() {
return INSTANCE;
}
public void print() {
System.out.println("Name "+name + ", age: "+age);
}
public static class SingletonBuilder {
private final String name; // Mandatory
private Double age = null; // Not Mandatory
private SingletonBuilder(){
name = null;
}
SingletonBuilder(String name) {
this.name = name;
}
public SingletonBuilder age(double age) {
this.age = age;
return this;
}
public void build(){
EnumSingleton.INSTANCE.build(this);
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后你可以创建/实例化/参数化它如下:
public static void main(String[] args) {
new EnumSingleton.SingletonBuilder("nico").age(41).build();
EnumSingleton.getSingleton().print();
}
Run Code Online (Sandbox Code Playgroud)
使用getter和setter设置变量并将默认构造函数设为私有.然后使用:
Singleton.getInstance().setX(value);
Run Code Online (Sandbox Code Playgroud)
小智 6
“ 带有参数的单例不是单例 ”语句并不完全正确。我们需要从应用程序的角度而不是从代码的角度进行分析。
我们构建单例类以在一个应用程序运行中创建对象的单个实例。通过使用带有参数的构造函数,您可以在代码中构建灵活性,以在每次运行应用程序时更改单例对象的某些属性。这不违反Singleton模式。如果从代码角度来看,这似乎是一种违规。
设计模式可以帮助我们编写灵活且可扩展的代码,而不会妨碍我们编写良好的代码。
很惊讶没有人提到如何创建/检索记录器.例如,下面显示了如何检索Log4J记录器.
// Retrieve a logger named according to the value of the name parameter. If the named logger already exists, then the existing instance will be returned. Otherwise, a new instance is created.
public static Logger getLogger(String name)
Run Code Online (Sandbox Code Playgroud)
有一些层次的间接,但关键部分是下面的方法,它几乎告诉它如何工作的一切.它使用哈希表来存储现有的记录器,密钥是从名称派生的.如果给定名称不存在记录器,它将使用工厂创建记录器,然后将其添加到哈希表中.
69 Hashtable ht;
...
258 public
259 Logger getLogger(String name, LoggerFactory factory) {
260 //System.out.println("getInstance("+name+") called.");
261 CategoryKey key = new CategoryKey(name);
262 // Synchronize to prevent write conflicts. Read conflicts (in
263 // getChainedLevel method) are possible only if variable
264 // assignments are non-atomic.
265 Logger logger;
266
267 synchronized(ht) {
268 Object o = ht.get(key);
269 if(o == null) {
270 logger = factory.makeNewLoggerInstance(name);
271 logger.setHierarchy(this);
272 ht.put(key, logger);
273 updateParents(logger);
274 return logger;
275 } else if(o instanceof Logger) {
276 return (Logger) o;
277 }
...
Run Code Online (Sandbox Code Playgroud)
oxb*_*kes -3
单例通常被认为是反模式,不应该使用。它们不会使代码易于测试。
无论如何,带有参数的单例是没有意义的 - 如果你写下会发生什么:
Singleton s = SingletonHolder.getInstance(1);
Singleton t = SingletonHolder.getInstance(2); //should probably throw IllegalStateException
Run Code Online (Sandbox Code Playgroud)
您的单例也不是线程安全的,因为多个线程可以同时调用,从而getInstance
导致创建多个实例(可能具有不同的 值x
)。
归档时间: |
|
查看次数: |
109654 次 |
最近记录: |