使用enum在Java中实现多圈

时间:2014-09-17 23:01:08

标签: java enums

我想拥有某个复杂界面实例的有限固定目录。标准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();
    }
}

这种模式有一些非常好的功能:

  • enum实现了使用它非常自然的接口:ComplexItem.ITEM1.method();
  • 懒惰的实例化:如果构造成本高昂(我的用例涉及阅读文件),只有在需要时才会出现。

说过这看起来非常复杂,而且很糟糕'对于这样一个简单的要求,并以一种我不确定语言设计者的意图的方式覆盖枚举方法。

它还有另一个显着的缺点。在我的用例中,我喜欢扩展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() { .... }
}

推荐背景阅读:Mixing-in an Enum

3 个答案:

答案 0 :(得分:2)

似乎很好。我会像这样做初始化线程安全:

enum ComplexItem implements Complex {
    ITEM1 {
        protected Complex makeInstance() {
            return new Complex() { public void method() { }};
        }
    },
    ITEM2 {
        protected Complex makeInstance() {
            return new Complex() { public void method() { }}
    };

    private volatile Complex instance;

    private Complex getInstance() {
        if (instance == null) {
            createInstance();
        }
        return instance;
    }

    protected abstract Complex makeInstance();

    protected synchronized void createInstance() {
        if (instance == null) {
            instance = makeInstance();
        }
    }

    public void method() {
        getInstance().method();
    }
}

修饰符synchronized仅显示在createInstance()方法上,但会将调用包裹到makeInstance() - 传达线程安全,而不会在调用getInstance()和没有程序员的情况下出现瓶颈必须记住将synchronized添加到每个makeInstance()实施中。

答案 1 :(得分:1)

关于这种模式的一个想法:懒惰的实例化不是线程安全的。这可能是也可能不是,这取决于你想如何使用它,但它值得了解。 (考虑到枚举初始化本身就是线程安全的。)

除此之外,我无法看到一个更简单的解决方案,可以保证完整的实例控制,直观且使用惰性实例化。

我也不认为它是滥用枚举方法,它与Josh Bloch的Effective Java建议将不同策略编码到枚举中的区别并不大。< / p>

答案 2 :(得分:1)

这对我有用 - 它是线程安全的和通用的。 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); } } } 的开销很小,但可以通过稍微调整来改善。

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()));
    }
}

在Java 8中,它更容易:

{{1}}