简体   繁体   English

Java中可配置的枚举

[英]Configurable enum in Java

I'm looking for a better pattern to implement something like this: 我正在寻找一种更好的模式来实现这样的东西:

public static enum Foo {
    VAL1( new Bar() ),
    VAL2( new FooBar() );

    private final bar;

    private Foo( IBar bar ) {
        this.bar = bar;
    }

    public IBar getBar() { return bar; }
}

The issue is that accessing the enum causes side effects. 问题是访问enum会产生副作用。 Say Bar opens a DB connection and the like. Bar打开一个数据库连接等。 So even if I just need VAL2 , I have to pay the price to setup VAL1 . 因此,即使我只需要VAL2 ,我也必须付出代价来设置VAL1

OTOH, the value of bar is tightly coupled to the enum . OTOH, bar的值与enum紧密相关。 It's like an static attribute but enum has no lazy initialization. 这就像一个静态属性,但enum没有延迟初始化。 I could make Foo.getBar() abstract and use anonymous classes but then, I would have to pay the setup price every time. 我可以将Foo.getBar()抽象化并使用匿名类,但随后,我将不得不每次都支付安装费用。

Is there a cheap way to add lazy init for attributes of enum s? 是否有一种便宜的方法可以为enum的属性添加惰性初始化?

[EDIT] TO make this clear: [编辑]明确说明:

  1. getBar() is called millions of times. getBar()被调用了数百万次。 It must be blinding fast. 它一定是令人眼花fast乱的。

  2. We're talking singleton here (just like enum itself). 我们在这里谈论单例(就像enum本身一样)。 Only a single instance must ever be created. 只能创建一个实例。

    For additional points, unit tests should be able to override this behavior. 对于其他要点,单元测试应该能够覆盖此行为。

  3. Instances must be created lazily. 必须延迟创建实例。

One solution we tried as to register the values as beans in Spring: 我们试图在Spring中将值注册为bean的一种解决方案:

<bean id="VAL1.bar" class="...." />

That allowed us to specify the values at runtime and override them in tests. 这使我们可以在运行时指定值,并在测试中覆盖它们。 Unfortunately, it means we have to inject the ApplicationContext into the enum somehow. 不幸的是,这意味着我们必须以某种方式将ApplicationContext注入enum So we need a global variable for that. 因此,我们需要一个全局变量。 cringe 低三下四

What's worse: Looking up the value in getBar() is way too slow. 更糟糕的是:在getBar()getBar()值太慢了。 We can synchronize getBar() and use if(bar!= null)bar=context.get(name()+".bar"); 我们可以synchronize getBar()并使用if(bar!= null)bar=context.get(name()+".bar"); to solve this. 解决这个问题。

But is there a way without this that is as safe and fast as using the enum values themselves? 但是,有没有一种方法可以像使用enum值本身一样安全,快捷呢?

Just replace the enum with an abstract factory pattern. 只需用抽象工厂模式替换枚举即可。

UPD : You can try something like this: UPD :您可以尝试以下操作:

public interface Factory {
   IBar getBar();
}

public class BarFactory implements Factory {

   private IBar barInstance;   

   public synchronized IBar getBar() {
       if (barInstance == null) {
          barInstance = new Bar();
       }
       return barInstance;
   }
}

public class FooBarFactory implements Factory {

   private IBar barInstance;   

   public synchronized IBar getBar() {
       if (barInstance == null) {
          barInstance = new FooBar();
       }
       return barInstance;
   }
}

You can try to optimize the synchronization part in some way but that way can vary depending on your concrete use cases. 您可以尝试以某种方式优化同步部分,但是这种方式可能会因您的具体用例而异。

You can add one level of indirection using a "value holder" which does the lazy initialization: 您可以使用执行延迟初始化的“值持有者”来添加一个间接级别:

abstract class BarHolder {
  IBar bar;

  abstract IBar createBar();

  IBar getBar() {
    if (bar == null) {
      bar = createBar();
    }
    return bar;
  }
}

Now, adjust your enumeration like this: 现在,像这样调整枚举:

public static enum Foo {
  VAL1(new BarHolder() {
    IBar createBar() { return new Bar(); }
  )},
  VAL2(new BarHolder() {
    IBar createBar() { return new FooBar(); }
  )};

  private final BarHolder barHolder;

  private Foo(BarHolder barHolder) {
    this.barHolder = barHolder;
  }

  public IBar getBar() { return barHolder.getBar(); }
}

Warning: Since this is NOT thread-safe , arbitrarily many instances of any IBar can be created. 警告:由于这不是线程安全的 ,因此可以创建任何IBar许多实例。 Therefore, this solution doesn't meet the Singleton requirement (#2 in the OP). 因此,此解决方案不符合Singleton要求(OP中的#2)。 And what is worse, getBar() can easily return a reference to a not-yet-initialised instance of an IBar . 更糟糕的是, getBar()可以轻松地返回对尚未初始化的IBar实例的IBar

Try to store not an object but a class in your enum and when you need, just instantiate it via Class.newInstance(). 尝试在枚举中存储的不是对象,而是类。在需要时,只需通过Class.newInstance()实例化即可。

public static enum Foo {
    VAL1(Bar.class),
    VAL2(FooBar.class);

    private final Class<...> barClass;

    private Foo( Class<? extends IBar> barClass ) {
        this.barClass = barClass;
    }

    public Class< ? extends IBar> getBarClass() { return barClass; }
}

/// client code
try {
IBar instance = Class.newInstance(Foo.VAL1.getBarClass());
} catch (...) {
...
}

The following is both thread-safe and lazy-initialized , combining two patterns: Enum Singleton and Initialization-On-Demand Holder . 以下内容是线程安全的延迟初始化的 ,结合了两种模式: Enum Singleton按需初始化Holder It is the only lazy way to avoid unnecessary synchronization , which in the case of Abstract Factory pattern occurs for every single getBar() call, whereas in this case it occurs only once in the event of Static Nested Class initialization, which is lazy by definition : 这是避免不必要的同步的唯一懒惰方式,在每次调用getBar() 都会发生Abstract Factory模式的情况下,而在这种情况下,在静态嵌套类初始化的情况下它只会发生一次 ,这在定义上懒惰的:

public enum Foo {
    VAL1() {
        @Override
        public IBar getBar() {
            return LazyVAL1.BAR;
        }
    },
    VAL2() {
        @Override
        public IBar getBar() {
            return LazyVAL2.BAR;
        }
    };

    private static class LazyVAL1 {
        public static final IBar BAR = new Bar();
    }

    private static class LazyVAL2 {
        public static final IBar BAR = new FooBar();
    }

    public abstract IBar getBar();
}

Based on Vladimir's answer - this could do it. 根据弗拉基米尔的回答-可以做到。 It will only create the class objects, the instances should be created lazily on demand: 它将仅创建类对象,应按需延迟创建实例:

public static enum Foo {
    VAL1(Bar.class),
    VAL2(FooBar.class);

    private Class<? extends IBar> clazz;
    private IBar bar = null;

    private Foo( Class<? extends IBar> clazz) {
        this.clazz = clazz;
    }

    public IBar getBar() {
      if (bar == null)
         bar = clazz.newInstance();
      }
      return bar;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM