枚举单例的一些问题:
致力于实施策略
通常,“单一”是指一种实施策略,而不是API规范。Foo1.getInstance()
公开声明它将始终返回同一实例的情况很少见。如果需要,例如的实现Foo1.getInstance()
可以发展为每个线程返回一个实例。
随着Foo2.INSTANCE
我们公开宣布这个实例的实例,而且也没有机会来改变这种状况。具有单个实例的实现策略已公开并落实。
这个问题并不严重。例如,Foo2.INSTANCE.doo()
可以依赖线程本地帮助器对象,以有效地具有每个线程的实例。
扩展枚举类
Foo2
扩展了一个超类Enum<Foo2>
。我们通常想避免超类;特别是在这种情况下,被强加的超类Foo2
与Foo2
假定的情况无关。这对我们应用程序的类型层次结构造成了污染。如果我们真的想要一个超类,通常它是一个应用程序类,但是我们不能,它Foo2
的超类是固定的。
Foo2
继承了一些有趣的实例方法(例如)name(), cardinal(), compareTo(Foo2)
,这些方法只会使Foo2
用户感到困惑。即使在接口中需要该方法Foo2
也不能拥有自己的name()
方法Foo2
。
Foo2
还包含一些有趣的静态方法
public static Foo2[] values() { ... }
public static Foo2 valueOf(String name) { ... }
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
这对用户而言似乎毫无意义。无论如何,单身人士通常都不应使用脉冲静态方法(除外getInstance()
)
可序列化
单身人士有状态是很常见的。这些单例通常不应该可序列化。我想不出任何现实的例子,将有状态的单例从一个VM传输到另一个VM是有意义的。单例表示“在VM中是唯一的”,而不是“在Universe中是唯一的”。
如果序列化对于有状态单例确实有意义,则该单例应明确且精确地指定在可能已存在相同类型单例的另一个VM中反序列化单例的含义。
Foo2
自动采用简单的序列化/反序列化策略。那只是一场等待发生的事故。如果我们有一个数据树,从概念上讲Foo2
在t1时引用了VM1中的状态变量,则通过序列化/反序列化,该值将变为不同的值- Foo2
在t2时VM2 中的相同变量的值,从而导致难以检测的bug。该错误不会Foo1
静默地出现在不可序列化的代码中。
编码限制
有一些事情可以在普通课堂上完成,但在enum
课堂上是被禁止的。例如,访问构造函数中的静态字段。程序员必须在特殊的班级工作,因此必须更加小心。
结论
通过搭载枚举,我们节省了两行代码;但是价格太高,我们必须背负枚举的所有包and和限制,我们无意间继承了枚举的“功能”,这些功能会带来意想不到的后果。唯一据称的优势-自动可序列化性-成为劣势。