2011-04-30 2 views
5

Шаблон для создания одиночек, кажется, что-то вроде:Java: Ленивый Initializing Singleton

public class Singleton { 
    private static final Singleton instance = new Singleton(); 
    private Singleton(){ 
    } 

    public static Singleton getInstance() 
    { 
     return instance; 
    } 
} 

Однако моя проблема заключается в том, как сделать вам блок с классом, как это, если Singleton Конструктор делает то, что не является модульное тестирование например, вызовы внешней службы, JNDI поиск и т.д.

Я думаю, я мог бы реорганизовать это нравится:

public class Singleton { 
    private static Singleton instance; 
    private Singleton(){ 
    } 

    public synchronized static Singleton getInstance() 
    { 
     if(instance == null) 
      instance = new Singleton(); 
     return instance; 
    } 

    //for the unit tests 
    public static void setInstance(Singleton s) 
    { 
      instancce = s; 
    } 
} 

Сейчас проблема заключается в том, что только за единицу контролируемости я заставил GetInstance быть синхронизированы так просто для тестирования аспекта это окажет негативное влияние на реальное применение. Есть ли способ обойти это, кажется, что любая другая ленивая инициализация не будет работать из-за нарушенной природы двойной блокировки в java.

+0

Вы определили ключевой недостаток шаблона Singleton: он блокирует вас в конкретной реализации интерфейса Singleton. Да, вы можете обойти это по-разному, ужасно уродливым. Вместо этого спросите себя, действительно ли вам нужен синглтон. – hifier

+0

Зачем вам когда-либо понадобилась ленивая инициализация того, что Java уже автоматически предоставляет для каждого типа? –

ответ

5

Вы можете использовать Factory pattern для создания синглтона, а также реализовать реализацию в зависимости от среды.

Или, избегайте использования одноэлементного рисунка и вместо этого используйте Dependency Injection.

9

Вы можете использовать перечисление как Singleton

enum Singleton { 
    INSTANCE; 
} 

Произнесите одноточечно делает что-то нежелательное в юнит-тестов, вы можете;

// in the unit test before using the Singleton, or any other global flag. 
System.setProperty("unit.testing", "true"); 

Singleton.INSTANCE.doSomething(); 

enum Singleton { 
    INSTANCE; 
    { 
     if(Boolean.getBoolean("unit.testing")) { 
      // is unit testing. 
     } else { 
      // normal operation. 
     } 
    } 
} 

Примечание: синхронизированных блоков или явного блокирования не требуется. INSTANCE не будет загружаться до тех пор, пока не будет доступ к .class и не будет инициализирован до тех пор, пока не будет использован элемент. если вы используете только Singleton.INSTANCE, а не Singleton.class, не будет проблем со значением, используемым для инициализации изменения позже.


Edit: если вы используете только Singleton.class это не может инициализировать класс. Это не в этом примере на Java 8 обновлений 112.

public class ClassInitMain { 
    public static void main(String[] args) { 
     System.out.println("Printing a class reference"); 
     Class clazz = Singleton.class; 
     System.out.println("clazz = " + clazz); 
     System.out.println("\nUsing an enum value"); 
     Singleton instance = Singleton.INSTANCE; 
    } 

    static enum Singleton { 
     INSTANCE; 

     Singleton() { 
      System.out.println(getClass() + " initialised"); 
     } 
    } 
} 

печатает

Printing a class reference 
clazz = class ClassInitMain$Singleton 

Using an enum value 
class ClassInitMain$Singleton initialised 
+1

Экземпляр будет загружен даже с помощью ссылки на .class, но не инициализирован. –

+0

@LewBloch Там могут быть JVM, где это происходит, но обновление Oracle JVM 8 112 не работает. –

+0

Неправильно. JLS объясняет. Очевидно, что тип загружает ссылку на литерал '.class', иначе литерал не будет в памяти ссылаться в первую очередь. Это загрузит тип, но не инициализирует его. Вероятно, вы путаете загрузку и инициализацию. –

2

Вы можете придать зависимости одноплодного экземпляра, переопределить GetInstance() из модульного тестирования коды, ориентированное использование аспекта программировать, чтобы перехватить вызов метода и вернуть другой объект, или использовать такой инструмент, как jmockit, который позволяет вам издеваться над чем угодно, включая статику, конечные классы, конструкторы и все, что обычно говорят люди, «неустойчивые».

Один подход, который я использовал в унаследованных системах (где я хотел сделать что-то проверяемое с минимальным воздействием на архитектуру системы), заключался в том, чтобы модифицировать фабричные методы (getInstance), чтобы проверить системное свойство для альтернативной реализации, что я вместо этого будет создан экземпляр. Это было задано альтернативным, макетным объектом в модульном наборе тестов.

Что касается оператора с двойной проверкой блокировки, это больше не так, если вы используете ключевое слово volatile и Java> = 1.5. Он был сломан (даже с изменчивым) с 1,4 и более ранними версиями, но если вы знаете, что ваш код будет работать только на последних JVM, я бы не стал беспокоиться об этом. Но я также не буду использовать синглтон в любом случае: использование контейнера DI/IOC для жизненного цикла объекта позволит решить обе ваши проблемы (тестируемость и узкое место для узкого доступа) намного элегантнее.

+0

hmm Мне нравится идея системного свойства, которое может отображаться для модульного тестирования. В идеале было бы неплохо изменить, чтобы не использовать Singleton, но его немного выше меня, большой старой базы кода и времени для повторного фактора :( – bluphoenix

0

Как насчет того, чтобы вы ленивы инициализировались на этапе сборки, где выполняются модульные тесты. Затем вы меняете код обратно на inline-инициализацию перед его компиляцией для распространения.

Ваш производственный код встроен в исходное состояние, за исключением ваших тестов. Возможно, это несоответствие между производством и тестированием кода может привести к ошибкам, но что?

(Разумеется, если это решение, мы даем возможность построить фазу + инструмент. Я вижу, что это облегчается с maven и dp4j).

3

Двойная проверка блокировки нарушена на всех языках, а не только на Java.

Я склонен отказаться от одиночек, но вы можете использовать шаблон держатель очень хорошо, если вы нуждаетесь в них, как это рекомендовано в Джоша Блоха Effective Java:

public class Foo 
{ 
    static class Holder 
    { 
    static final Foo instance = new Foo(); 
    } 

    public static Foo getInstance() 
    { 
    return Holder.instance; 
    } 

    private Foo() 
    { 
    } 

    // ... 
} 

EDIT: Капремонт ссылка.

+0

Хотя я упомянул решение класса держателей для реализации синглетов, это почти всегда простейший, самый надежный и почти всегда правильный подход - определить перечисление с одной константой, как указал @Peter Lawrey. –