【发布时间】:2014-09-17 23:01:07
【问题描述】:
我想要一个有限的固定目录,包含某个复杂接口的实例。标准的multiton 模式有一些不错的特性,比如惰性实例化。然而,它依赖于一个键,例如一个看起来很容易出错和脆弱的字符串。
我想要一个使用枚举的模式。它们有很多很棒的功能并且很健壮。我试图为此找到一个标准的设计模式,但已经画了一个空白。所以我想出了我自己的,但我对它不是很满意。
我使用的模式如下(界面在这里高度简化以使其可读):
interface Complex {
void method();
}
enum ComplexItem implements Complex {
ITEM1 {
protected Complex makeInstance() { return new Complex() { ... }
},
ITEM2 {
protected Complex makeInstance() { return new Complex() { ... }
};
private Complex instance = null;
private Complex getInstance() {
if (instance == null) {
instance = makeInstance();
}
return instance;
}
protected void makeInstance() {
}
void method {
getInstance().method();
}
}
这个模式有一些非常好的特性:
- 枚举实现了使其使用非常自然的接口:ComplexItem.ITEM1.method();
- 延迟实例化:如果构建成本很高(我的用例涉及读取文件),则仅在需要时才会发生。
话虽如此,对于这样一个简单的要求,它似乎非常复杂和“hacky”,并且以我不确定语言设计者的意图的方式覆盖枚举方法。
它还有另一个明显的缺点。在我的用例中,我希望接口扩展 Comparable。不幸的是,这与 Comparable 的枚举实现发生冲突,使代码无法编译。
我考虑的另一种选择是使用标准枚举,然后使用一个单独的类将枚举映射到接口的实现(使用标准多点模式)。这有效,但枚举不再实现在我看来不是意图的自然反映的接口。它还将接口的实现与似乎封装不佳的枚举项分开。
另一种选择是让枚举构造函数实现接口(即在上面的模式中删除对“makeInstance”方法的需要)。虽然这有效,但它消除了仅在需要时运行构造函数的优势)。它也不能解决扩展 Comparable 的问题。
所以我的问题是:谁能想到更优雅的方式来做到这一点?
作为对 cmets 的回应,我将尝试指定我尝试首先解决的具体问题,然后通过一个示例来解决。
- 有一组固定的对象实现给定的接口
- 对象是无状态的:它们仅用于封装行为
- 每次执行代码时只会使用对象的一个子集(取决于用户输入)
- 创建这些对象的成本很高:只应在需要时创建一次
- 对象共享很多行为
这可以通过为每个对象使用单独的类或超类来实现共享行为的单独的单例类来实现。这似乎不必要地复杂。
现在举个例子。系统在一组区域中计算几种不同的税收,每个区域都有自己的税收计算算法。预计区域集永远不会改变,但区域算法会定期改变。特定的区域费率必须在运行时通过缓慢且昂贵的远程服务加载。每次调用系统时,都会给它一组不同的区域进行计算,因此它应该只加载请求的区域的速率。
所以:
interface TaxCalculation {
float calculateSalesTax(SaleData data);
float calculateLandTax(LandData data);
....
}
enum TaxRegion implements TaxCalculation {
NORTH, NORTH_EAST, SOUTH, EAST, WEST, CENTRAL .... ;
private loadRegionalDataFromRemoteServer() { .... }
}
推荐背景阅读:Mixing-in an Enum
【问题讨论】:
-
我认为我同意您展示的解决方案是骇人听闻且过于复杂的。我认为您应该使用最简单的枚举,然后只使用静态工厂。工厂应该缓存值并在需要时创建新值。
-
延迟加载、从文件中读取、有限的实例集......对我来说听起来像是一堆单身人士。
-
枚举应该是不可变的和可序列化的,因此违反它似乎可能会导致问题。
-
@wolfcastle 好吧,它们实际上是不可变的。 (如果同步是固定的。)
-
我有一个 - 在我看来 - 优雅的
enum键控Multiton。我明天会发布它,因为我现在没有副本。