Java 中可配置的枚举

发布于 2024-10-04 01:30:21 字数 1252 浏览 5 评论 0 原文

我正在寻找一种更好的模式来实现这样的事情:

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

问题是访问enum会导致副作用。假设 Bar 打开数据库连接等。因此,即使我只需要 VAL2,我也必须支付设置 VAL1 的费用。

OTOH,bar 的值与 enum 紧密耦合。它就像一个静态属性,但 enum 没有延迟初始化。我可以将 Foo.getBar() 抽象化并使用匿名类,但是这样我每次都必须支付设置费用。

有没有一种廉价的方法来为 enum 的属性添加延迟初始化?

[编辑] 为了明确这一点:

  1. getBar() 被调用了数百万次。速度一定快得让人眼花缭乱。

  2. 我们在这里谈论单例(就像enum本身)。只能创建一个实例。

    对于其他要点,单元测试应该能够覆盖此行为。

  3. 实例必须延迟创建。

我们尝试的一种解决方案是在 Spring 中将值注册为 beans:

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

这允许我们在运行时指定值并在测试中覆盖它们。不幸的是,这意味着我们必须以某种方式将 ApplicationContext 注入到 enum 中。所以我们需要一个全局变量。 畏缩

更糟糕的是:在 getBar() 中查找值太慢了。我们可以同步 getBar()并使用if(bar!= null)bar=context.get(name()+".bar");代码> 来解决这个问题。

但是,有没有一种方法可以不使用此方法,并且与使用枚举值本身一样安全和快速呢?

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. Say Bar opens a DB connection and the like. So even if I just need VAL2, I have to pay the price to setup VAL1.

OTOH, the value of bar is tightly coupled to the enum. It's like an static attribute but enum has no lazy initialization. I could make Foo.getBar() abstract and use anonymous classes but then, I would have to pay the setup price every time.

Is there a cheap way to add lazy init for attributes of enums?

[EDIT] TO make this clear:

  1. getBar() is called millions of times. It must be blinding fast.

  2. We're talking singleton here (just like enum itself). 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:

<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. So we need a global variable for that. cringe

What's worse: Looking up the value in getBar() is way too slow. We can synchronize getBar() and use 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

早茶月光 2024-10-11 01:30:21

只需用抽象工厂模式替换枚举即可。

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

您可以尝试以某种方式优化同步部分,但这种方式可能会根据您的具体用例而有所不同。

Just replace the enum with an abstract factory pattern.

UPD: You can try something like this:

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.

绮筵 2024-10-11 01:30:21

您可以使用执行延迟初始化的“值持有者”添加一级间接寻址:

abstract class BarHolder {
  IBar bar;

  abstract IBar createBar();

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

现在,像这样调整枚举:

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

警告:因为这是不是线程安全的 ,可以创建任何 IBar 的任意多个实例。因此,这个解决方案不满足单例要求(OP中的#2)。更糟糕的是,getBar() 可以轻松返回对尚未初始化的 IBar 实例的引用。

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. Therefore, this solution doesn't meet the Singleton requirement (#2 in the OP). And what is worse, getBar() can easily return a reference to a not-yet-initialised instance of an IBar.

贵在坚持 2024-10-11 01:30:21

尝试在枚举中存储一个类而不是一个对象,当需要时,只需通过 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 (...) {
...
}

Try to store not an object but a class in your enum and when you need, just instantiate it via 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 (...) {
...
}
‖放下 2024-10-11 01:30:21

以下是线程安全延迟初始化,结合了两种模式:枚举单例 按需初始化支架。这是避免不必要的同步的唯一惰性方法,在抽象工厂模式发生在每个单个getBar()调用中,而在本例中它发生 >仅一次,如果静态嵌套类初始化,这是惰性定义

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

The following is both thread-safe and lazy-initialized, combining two patterns: Enum Singleton and Initialization-On-Demand 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:

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();
}
捎一片雪花 2024-10-11 01:30:21

根据弗拉基米尔的回答 - 这可以做到。它只会创建类对象,实例应该根据需要延迟创建:

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

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;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文