Java: ленивая инициализация синглтона - PullRequest
7 голосов
/ 30 апреля 2011

Шаблон для создания синглетонов выглядит примерно так:

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.

Ответы [ 5 ]

11 голосов
/ 30 апреля 2011

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

enum Singleton {
    INSTANCE;
}

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


Редактировать: если вы используете только 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
6 голосов
/ 30 апреля 2011

Можно использовать шаблон Factory для создания синглтона и переключения реализаций в зависимости от среды.

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

4 голосов
/ 21 марта 2012

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

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

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

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

  private Foo()
  {
  }

  // ...
}

РЕДАКТИРОВАТЬ: Исправлена ​​ссылка.

2 голосов
/ 30 апреля 2011

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

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

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

0 голосов
/ 30 апреля 2011

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

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

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

...