我想有一个有限的固定目录的某个复杂接口的实例.标准的multiton模式有一些很好的功能,比如延迟实例化.但是它依赖于一个像String这样的键,它看起来很容易出错并且很脆弱.
我想要一个使用枚举的模式.它们有很多很棒的功能,非常强大.我试图找到一个标准的设计模式,但已经画了一个空白.所以我想出了自己的,但我对它并不十分满意.
我正在使用的模式如下(这里的界面高度简化,使其可读):
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();
}
}
Run Code Online (Sandbox Code Playgroud)
这种模式有一些非常好的功能:
说过这样一个简单的要求似乎非常复杂和"hacky",并以一种我不确定语言设计者的意图的方式覆盖枚举方法.
它还有另一个显着的缺点.在我的用例中,我希望扩展Comparable的界面.不幸的是,这与Comparable的枚举实现冲突并使代码无法编译.
我考虑过的一个替代方案是使用标准枚举,然后是一个单独的类,它将枚举映射到接口的实现(使用标准多重模式).这是有效的,但是枚举不再实现界面,在我看来这不是对意图的自然反映.它还将接口的实现与枚举项分开,这似乎是不良的封装.
另一种方法是让枚举构造函数实现接口(即在上面的模式中删除'makeInstance'方法的需要).虽然这样可行,但它消除了仅在需要时运行构造函数的优点.它也无法解决扩展Comparable的问题.
所以我的问题是:有人能想到更优雅的方式吗?
在回应评论时,我会试着指出我试图首先解决的具体问题然后通过一个例子.
这可以使用单独的类或超类来为每个对象使用单独的单例类来实现共享行为.这似乎不必要地复杂.
现在举个例子.系统在一组区域中计算几种不同的税,每个区域都有自己的算法来计算税收.这组区域预计永远不会改变,但区域算法会定期更改.必须在运行时通过缓慢且昂贵的远程服务加载特定的区域费率.每次调用系统时,都会给出一组不同的区域来计算,因此它应该只加载所请求区域的速率.
所以:
interface TaxCalculation {
float calculateSalesTax(SaleData data);
float calculateLandTax(LandData data);
....
}
enum TaxRegion implements TaxCalculation {
NORTH, NORTH_EAST, SOUTH, EAST, WEST, CENTRAL .... ;
private loadRegionalDataFromRemoteServer() { .... }
}
Run Code Online (Sandbox Code Playgroud)
推荐背景阅读:混入Enum
这对我有用 - 它是线程安全且通用的。必须enum实现该Creator接口,但这很容易 - 正如最后的示例用法所示。
enum此解决方案打破了您在存储对象处强加的绑定。在这里,我只使用作为enum工厂来创建对象 - 通过这种方式,我可以存储任何类型的对象,甚至让每个对象enum创建不同类型的对象(这是我的目标)。
ConcurrentMap这使用了线程安全和延迟实例化的通用机制FutureTask。
在程序的整个生命周期中保留 的开销很小FutureTask,但是可以通过一些调整来改进。
/**
* A Multiton where the keys are an enum and each key can create its own value.
*
* The create method of the key enum is guaranteed to only be called once.
*
* Probably worth making your Multiton static to avoid duplication.
*
* @param <K> - The enum that is the key in the map and also does the creation.
*/
public class Multiton<K extends Enum<K> & Multiton.Creator> {
// The map to the future.
private final ConcurrentMap<K, Future<Object>> multitons = new ConcurrentHashMap<K, Future<Object>>();
// The enums must create
public interface Creator {
public abstract Object create();
}
// The getter.
public <V> V get(final K key, Class<V> type) {
// Has it run yet?
Future<Object> f = multitons.get(key);
if (f == null) {
// No! Make the task that runs it.
FutureTask<Object> ft = new FutureTask<Object>(
new Callable() {
public Object call() throws Exception {
// Only do the create when called to do so.
return key.create();
}
});
// Only put if not there.
f = multitons.putIfAbsent(key, ft);
if (f == null) {
// We replaced null so we successfully put. We were first!
f = ft;
// Initiate the task.
ft.run();
}
}
try {
/**
* If code gets here and hangs due to f.status = 0 (FutureTask.NEW)
* then you are trying to get from your Multiton in your creator.
*
* Cannot check for that without unnecessarily complex code.
*
* Perhaps could use get with timeout.
*/
// Cast here to force the right type.
return type.cast(f.get());
} catch (Exception ex) {
// Hide exceptions without discarding them.
throw new RuntimeException(ex);
}
}
enum E implements Creator {
A {
public String create() {
return "Face";
}
},
B {
public Integer create() {
return 0xFace;
}
},
C {
public Void create() {
return null;
}
};
}
public static void main(String args[]) {
try {
Multiton<E> m = new Multiton<E>();
String face1 = m.get(E.A, String.class);
Integer face2 = m.get(E.B, Integer.class);
System.out.println("Face1: " + face1 + " Face2: " + Integer.toHexString(face2));
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
}
Run Code Online (Sandbox Code Playgroud)
在 Java 8 中甚至更容易:
public class Multiton<K extends Enum<K> & Multiton.Creator> {
private final ConcurrentMap<K, Object> multitons = new ConcurrentHashMap<>();
// The enums must create
public interface Creator {
public abstract Object create();
}
// The getter.
public <V> V get(final K key, Class<V> type) {
return type.cast(multitons.computeIfAbsent(key, k -> k.create()));
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
859 次 |
| 最近记录: |