2010-11-19 1 views
2

Я ищу лучший шаблон для реализации что-то вроде этого:Настраиваемый перечисление в Java

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() и использовать анонимные классы, но тогда мне придется платить за установку каждый раз.

Есть ли дешевый способ добавить ленивый init для атрибутов enum s?

[EDIT] сделать это ясно:

  1. getBar() называется миллионы раз. Это должно быть ослепляющим быстро.

  2. Мы говорим о синглете здесь (точно так же, как и enum). Должен быть создан только один экземпляр.

    Для дополнительных целей, модульные тесты должны иметь возможность отменить это поведение.

  3. Экземпляры должны создаваться лениво.

Одним из решений мы старались, чтобы зарегистрировать значения, как бобы весной:

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

Это позволило нам определить значения во время выполнения и переопределить их в тестах. К сожалению, это означает, что мы должны ввести ApplicationContext в enum. Поэтому для этого нам нужна глобальная переменная. cringe

Что еще хуже: поиск значения в getBar() слишком медленный. Мы можем synchronizegetBar() и использовать if(bar!= null)bar=context.get(name()+".bar");, чтобы решить эту проблему.

Но есть ли способ без этого, который так же безопасен и быстр, как и сами значения enum?

+0

ли они все должны быть в том же перечислении? Если вы используете разные перечисления, каждый будет загружать только одну (или одну группу) значений. –

+0

Все они принадлежат к тому же перечислению. Распространение их по нескольким перечислениям не имеет смысла с логической/семитической точки зрения. –

ответ

1

Попробуйте сохранить не объект, а класс в вашем перечислении, и когда вам нужно, просто создайте его через 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 (...) { 
... 
} 
+0

Как клиенты должны убедиться, что создан только один экземпляр каждого значения? –

2

Просто замените перечисление на абстрактный шаблон фабрики.

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

Вы можете попытаться оптимизировать часть синхронизации в некотором роде, но таким образом может меняться в зависимости от конкретных случаев использования.

+0

Я думаю, что это лучшее решение. У вас есть код? Как вы убедитесь, что каждое значение создается * lazy * и * только один раз * (например, singleton)? –

2

Вы можете добавить один уровень косвенности, используя «держатель значения», который делает ленивую инициализацию:

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. Поэтому это решение не соответствует требованию Singleton (№ 2 в OP). И что еще хуже, getBar() может легко вернуть ссылку на еще не инициализированный экземпляр IBar.

+0

Это не решает мою проблему, потому что она создает новый экземпляр каждый раз, когда вы вызываете 'getBar()'. –

+1

Он создает экземпляр ровно один раз (см. BarHolder.getBar()). – chris

+0

+1 Прости, пропустил это полностью. –

0

Основываясь на ответе Владимира - это может быть сделано. Это будет только создавать объекты класса, экземпляры должны быть созданы лениво по запросу:

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

Можете ли вы сделать эту работу многопотоковой без использования 'synchronize'? Это доступ как примитивное значение (миллионы раз в циклах и везде в коде), и я боюсь стоимости. –

1

Ниже как поточно- и отложенной инициализации, сочетающий в себе две модели: Enum Singleton и Initialization-On-Demand Holder. Это единственный ленивого способ избежать ненужной синхронизации, что в случае Abstract Factory рисунка происходит за каждый одного getBar() вызова, тогда как в данном случае это происходит только раз в случае Static Nested Class инициализации, которая ленивым по 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(); 
}