我最近在对同事进行的代码审查中遇到了一种以前从未见过的特殊模式。他正在将单例作为函数中的参数发送,并将其作为成员保存在调用类中。
假设我有一个简单的单例类Foo
public class Foo {
private static Foo instance;
private int counter;
private Foo(){}
public static Foo getInstance(){
if (instance == null){
instance = new Foo();
}
return instance;
}
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
}
}
现在有一个Foo
的客户叫Bar
:
public class Bar {
private Foo foo;
public Bar(Foo foo){
this.foo = foo;
this.foo.setCounter(10);
this.foo.setCounter(20);
}
public int getCounter(){
return foo.getCounter();
}
}
因此Bar
做2件不寻常的事情-1.在构造函数中调用Foo
,而不仅仅是使用Foo.getInstance()
和2.将Foo
保存为成员,以便于'保存”调用Foo.getInstance()
的样板就使用它。当然,我展示的用法是微不足道的。
这对我来说看起来很奇怪和尴尬,更糟糕的是,我无法在Foo
范围内将Bar
识别为单例。如果我在状态中更改了一些关键的内容怎么办?但是除了代码可读性的原因之外,我不能说这样做不是可选的,用法似乎最终是有效的。我是对还是错?出于什么原因?通过此单例并将其保存为成员是否正确?
答案 0 :(得分:2)
这似乎真的很主观。如果您认为代码在某个时候可能不再是单身人士,或者如果您认为自己可能会迁移到带有依赖项注入的Spring之类的框架中,那么引用单身人士将很方便。因此,您可以说这期待着未来的灵活性。通常,单例被视为与真正的面向对象的代码相反。
但是同时,如果您的应用程序严重依赖单例并且这是一个非常典型的模式……并且如果您担心由于这种用法而以某种方式最终可能会遇到单例类的重复实例,那么可以认为该代码与您的应用程序的通用模式和使用预期不匹配,并且应该遵循预期的准则。
在不了解您的应用程序,团队等更多信息的情况下,很难给出比这更具体的答案。
答案 1 :(得分:2)
不是常规的,但我认为根据用法:
1)将单例作为参数传递给另一个类的构造函数可能是有效的。
2)存储来自客户端的单例实例也可能是有效的。
国际奥委会可以做到这一点。
现在,如果您想向getInstance()
添加一些逻辑,则可能是不可取的:缓存实例,根据时间轴,客户端等返回不同的实例,等等……但这确实是实现有这么多的单身逻辑吗?不确定...
这对我来说看起来很奇怪和尴尬,更糟糕的是,我无法确定Foo是 在Bar范围内根本没有一个单例。
但是单例模式也很尴尬。如果单例模式向客户端公开单例实例的引用,则这些应可以自由使用。
为什么您认为在查看条形码时应该知道Foo是单身人士?
Bar取决于Foo,因为它需要Foo。实现(单个或否)在其级别上不重要。
通常应避免使用“硬编码”单例模式:一种全局变量,较难切换到其他实现,较难模拟...
如果您可以使用不难用实际代码实现的Java 5枚举,那就更好了。
实际单身人士的附加说明:
它不是线程安全的。使用比尔·普格成语会很容易做到这一点。
public class Foo {
private static class Holder{
private static Foo instance = new Foo();
}
private Foo(){}
public static Foo getInstance(){
return Holder.instance();
}
}
这与您的原始代码中的实例化类似。
但是,通常来说,要保留不昂贵且必不可少的实例化。
急于求成的方式通常是首选:
public class Foo {
private static Foo instance = new Foo();
private Foo(){}
public static Foo getInstance(){
return instance;
}
}