使用枚举在Java中实现多个 [英] Using enum to implement multitons in Java

查看:139
本文介绍了使用枚举在Java中实现多个的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要一个有限的固定目录的某个复杂的接口的实例。标准的 multiton 模式有一些不错的功能,如懒惰实例化。然而,它依赖于一个关键字,如字符串,这似乎很容易出错和脆弱。



我想要一个使用枚举的模式。他们有很多伟大的功能和强大的。我试图找到一个标准的设计模式,但画了一个空白。所以我想出了自己的,但我并不非常满意。



我使用的模式如下(界面在这里被高度简化了使其可读):

  interface Complex {
void method();
}

枚举ComplexItem实现复杂{
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()。
}
}

此模式有一些非常好的功能: / p>


  • 枚举实现了使其使用非常自然的界面:ComplexItem.ITEM1.method();

  • 懒惰实例化:如果建设成本高(我的用例涉及阅读文件),只有在需要时才会发生。



说,对于这样一个简单的要求来说,似乎非常复杂和黑客,并以我不能确定语言设计者的意图来覆盖枚举方法。



它也是有另一个显着的缺点。在我的用例中,我希望接口扩展可比性。不幸的是,这然后与Comparable的枚举实现冲突,并使代码无法编译。



我认为一个备选方案是使用标准枚举,然后是一个单独的类,将枚举映射到接口的实现(使用标准多频模式)。这个工作,但枚举不再实现我似乎不能自然地反映意图的接口。它也将接口的实现与枚举项目分开,这似乎是封装不好的。



另一个选择是让枚举构造函数实现接口(即在模式中以上删除了对'makeInstance'方法的需要)。虽然这样工作,它消除了仅在需要时运行构造函数的优点)。它也不能解决扩展Comparable的问题。



所以我的问题是:任何人都可以想到一个更优雅的方式来做到这一点?



为了回应评论,我会尝试指定一个具体的问题,我一般要解决,然后通过一个例子。




  • 有一组固定对象实现给定的界面

  • 对象是无状态的:它们仅用于封装行为

  • 每次执行代码时,只会使用一部分对象(取决于用户输入)

  • 创建这些对象是昂贵的:只应该一次完成如果需要

  • 对象共享很多行为



这可以用单独的单例类实现为每个对象使用单独的类或超类用于共享行为。这似乎是不必要的复杂。



现在一个例子。系统在一组区域中计算几种不同的税收,每个区域都有自己的计算税收的算法。预计该地区将不会发生变化,但区域算法将不断变化。具体的区域费率必须在运行时通过远程服务加载,这是一个缓慢而昂贵的。每次系统被调用时,都会给出一组不同的区域进行计算,因此只能加载所要求的区域的费率。



所以:

 界面TaxCalculation {
float calculateSalesTax(SaleData data);
float calculateLandTax(LandData data);
....
}

枚举TaxRegion实施TaxCalculation {
NORTH,NORTH_EAST,SOUTH,EAST,WEST,CENTRAL ....;

private loadRegionalDataFromRemoteServer(){...}
}

推荐背景阅读:混合使用枚举

解决方案

这对我来说 - 这是线程安全和通用的。 枚举必须实现 Creator 接口,但这很简单 - 最后的示例用法就证明了这一点。 p>

这个解决方案打破了你所施加的绑定,它是作为存储对象的枚举。在这里,我只使用枚举作为工厂来创建对象 - 这样我可以存储任何类型的对象,甚至每个枚举创建一个不同类型的对象(这是我的目标)。



这使用一个常用的线程安全和延迟实例化机制,使用 ConcurrentMap FutureTask



FutureTask 在程序的一生中,但可以通过一点调整来改进。

 code> / ** 
*一个Multiton,其中的键是枚举,每个键都可以创建自己的值。
*
*密钥枚举的create方法只能被调用一次。
*
*可能值得让您的Multiton静态以避免重复。
*
* @param< K> - 枚举是地图中的关键,也是创作。
* /
public class Multiton< K extends Enum< K> &安培; Multiton.Creator> {
//将来的地图。
private final ConcurrentMap< K,Future< Object>> multitons = new ConcurrentHashMap< K,Future< Object>>();

//枚举必须创建
public interface Creator {
public abstract Object create();

}

//吸气剂。
public< V> V get(final K key,Class< V> type){
//是否运行?
未来< Object> f = multitons.get(key);
if(f == null){
//不!做任务运行它。
FutureTask< Object> ft = new FutureTask< Object>(
new Callable(){

public Object call()throws Exception {
//只有在调用时才可以这样做。 b $ b return key.create();
}

});
//只有不在那里。
f = multitons.putIfAbsent(key,ft);
if(f == null){
//我们替换了null,所以我们成功放了。我们是第一!
f = ft;
//启动任务。
ft.run();
}
}
尝试{
/ **
*如果代码由于f.status = 0(FutureTask.NEW)而在这里挂起
*那么你正在尝试从你的创建者的Multiton中获得。
*
*没有不必要的复杂代码就无法检查。
*
*也许可以使用get超时。
* /
//在这里强制强制正确的类型。
return type.cast(f.get());
} catch(Exception ex){
//隐藏异常而不丢弃它们。
抛出新的RuntimeException(ex);
}
}

枚举E实现创建者{
A {

public String create(){
returnFace ;
}

},
B {

public Integer create(){
return 0xFace;
}

},
C {

public Void create(){
return null;
}

};
}

public static void main(String args []){
try {
Multiton& m = new Multiton E();
String face1 = m.get(E.A,String.class);
整数face2 = m.get(E.B,Integer.class);
System.out.println(Face1:+ face1 +Face2:+ Integer.toHexString(face2));
} catch(Throwable t){
t.printStackTrace(System.err);
}
}

}

在Java 8它更容易:

  public class Multiton< K extends Enum< K> &安培; Multiton.Creator> {

private final ConcurrentMap< K,Object> multitons = new ConcurrentHashMap<>();

//枚举必须创建
public interface Creator {

public abstract Object create();

}

//吸气剂。
public< V> V get(final K key,Class< V> type){
return type.cast(multitons.computeIfAbsent(key,k - > k.create()));
}
}


I would like to have a limited fixed catalogue of instances of a certain complex interface. The standard multiton pattern has some nice features such as lazy instantiation. However it relies on a key such as a String which seems quite error prone and fragile.

I'd like a pattern that uses enum. They have lots of great features and are robust. I've tried to find a standard design pattern for this but have drawn a blank. So I've come up with my own but I'm not terribly happy with it.

The pattern I'm using is as follows (the interface is highly simplified here to make it readable):

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

This pattern has some very nice features to it:

  • the enum implements the interface which makes its usage pretty natural: ComplexItem.ITEM1.method();
  • Lazy instantiation: if the construction is costly (my use case involves reading files), it only occurs if it's required.

Having said that it seems horribly complex and 'hacky' for such a simple requirement and overrides enum methods in a way which I'm not sure the language designers intended.

It also has another significant disadvantage. In my use case I'd like the interface to extend Comparable. Unfortunately this then clashes with the enum implementation of Comparable and makes the code uncompilable.

One alternative I considered was having a standard enum and then a separate class that maps the enum to an implementation of the interface (using the standard multiton pattern). That works but the enum no longer implements the interface which seems to me to not be a natural reflection of the intention. It also separates the implementation of the interface from the enum items which seems to be poor encapsulation.

Another alternative is to have the enum constructor implement the interface (i.e. in the pattern above remove the need for the 'makeInstance' method). While this works it removes the advantage of only running the constructors if required). It also doesn't resolve the issue with extending Comparable.

So my question is: can anyone think of a more elegant way to do this?

In response to comments I'll tried to specify the specific problem I'm trying to solve first generically and then through an example.

  • There are a fixed set of objects that implement a given interface
  • The objects are stateless: they are used to encapsulate behaviour only
  • Only a subset of the objects will be used each time the code is executed (depending on user input)
  • Creating these objects is expensive: it should only be done once and only if required
  • The objects share a lot behaviour

This could be implemented with separate singleton classes for each object using separate classes or superclasses for shared behaviour. This seems unnecessarily complex.

Now an example. A system calculates several different taxes in a set of regions each of which has their own algorithm for calculting the taxes. The set of regions is expected to never change but the regional algorithms will change regularly. The specific regional rates must be loaded at run time via remote service which is slow and expensive. Each time the system is invoked it will be given a different set of regions to calculate so it should only load the rates of the regions requested.

So:

interface TaxCalculation {
    float calculateSalesTax(SaleData data);
    float calculateLandTax(LandData data);
    ....
}

enum TaxRegion implements TaxCalculation {
    NORTH, NORTH_EAST, SOUTH, EAST, WEST, CENTRAL .... ;

    private loadRegionalDataFromRemoteServer() { .... }
}

Recommended background reading: Mixing-in an Enum

解决方案

This works for me - it's thread-safe and generic. The enum must implement the Creator interface but that is easy - as demonstrated by the sample usage at the end.

This solution breaks the binding you have imposed where it is the enum that is the stored object. Here I only use the enum as a factory to create the object - in this way I can store any type of object and even have each enum create a different type of object (which was my aim).

This uses a common mechanism for thread-safety and lazy instantiation using ConcurrentMap of FutureTask.

There is a small overhead of holding on to the FutureTask for the lifetime of the program but that could be improved with a little tweaking.

/**
 * 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);
    }
  }

}

In Java 8 it is even easier:

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中实现多个的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆