Трудность понимания, почему подклассы синглтона противоречат определению синглтона? - PullRequest
3 голосов
/ 22 июня 2019

Из других ответов здесь я вижу, что существенное разделение на подклассы синглтона противоречит тому, что значит иметь синглтон.Однако я не совсем понимаю, почему он нарушает это свойство, когда getInstance возвращает тот же общий экземпляр суперкласса.Например:

class Singleton {
    private static Singleton instance = null;
    protected Singleton() {

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

class SubclassSingleton extends Singleton {
    public SubclassSingleton() {

    }
}


SubclassSingleton x =  new SubclassSingleton();
SubclassSingleton y = new SubclassSingleton();
System.out.println(x.getInstance() == y.getInstance()); // true

Да, технически x и y являются экземплярами Singleton, но получение их экземпляра все еще приводит к общему ресурсу и конструктору (из моего понимания) после созданияx по сути не имеет никакого эффекта - скорее, статический метод getInstance() вернет разделяемую конструкцию родительского класса.Итак, мне трудно понять, как это нарушает, по сути, наличие одного общего экземпляра Singleton, несмотря на то, что он находится в подклассе.

Ответы [ 3 ]

4 голосов
/ 22 июня 2019

tl; dr : хотя я думаю, что можно создать синглтон таким образом, чтобы его можно было разделить на подклассы, класс должен быть разработан с особой тщательностью. В большинстве случаев я думаю, что это не стоит усилий.


Я бы не сказал, что создание подклассов является неотъемлемым нарушением принципа синглтона, но при разработке такого класса необходимо соблюдать особую осторожность. Чтобы понять почему, давайте взглянем на противоположные определения в этой проблеме.

Первое определение - это Singleton Pattern . По сути, это говорит о том, что для этого класса будет не более одного экземпляра этого класса, и все классы используют этот же экземпляр. В Java это обычно означает, что синглтон не может быть повторно инициализирован (т. Е. Должен быть предотвращен второй вызов new ...). Зачем? Потому что где-то в вашей программе какая-то часть может содержать ссылку x на «старый» экземпляр синглтона. Когда вы повторно инициализируете синглтон, x все равно будет ссылаться на старый синглтон, в то время как все будущие вызовы метода доступа синглтона будут содержать ссылку на новый синглтон. У вас есть два экземпляра одного класса, поэтому вы нарушаете синглтон.

Определение против единственного wrt. подкласс - это законы о замене Лискова . По сути, в нем говорится, что каждый подкласс класса X должен использоваться в месте X, и программа все еще должна работать. Вот почему Square и Rectangle никогда не должны стоять - это отношение наследования (одному нужно определить только одно свойство [length], а другому - два [width & height]).

Это означает, что для области действия синглтона все экземпляры подкласса также считаются экземплярами суперкласса. Вы должны не только управлять созданием рассматриваемого класса, но также и всех (!) Его подклассов, даже тех, которые не находятся под вашим контролем. В худшем случае вы даже не знаете всех классов, которые подкласс вашего синглтона.

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

class Singleton {
  private static volatile Singleton INSTANCE = null;

  private static Supplier<? extends Singleton> factory;

  public static void setSingletonFactory(Supplier<? extends Singleton> factory) {
    Singleton.factory = factory;
  }

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      synchronized (Singleton.class) {
        if (INSTANCE == null) {
          INSTANCE = factory.get();
        }
      }
    }
    return INSTANCE;
  }

  protected Singleton() {
    if (INSTANCE == null) {
      synchronized (Singleton.class) {
        if (INSTANCE == null) {
          // Set attribute of Singleton as necessary
        } else {
          throw cannotReinitializeSingletonIllegalStateException();
        }
      }
    } else {
      throw cannotReinitializeSingletonIllegalStateException();
    }
  }

  private static IllegalStateException cannotReinitializeSingletonIllegalStateException() {
    return new IllegalStateException("Cannot reinitialize Singleton");
  }

  public static void main(String... args) {
    Singleton.setSingletonFactory(SubSingleton.SUB_SINGLETON_FACTORY);
    Singleton instanceOne = Singleton.getInstance();
    Singleton instanceTwo = Singleton.getInstance();

    System.out.println(instanceOne == instanceTwo);

    try {
      SubSingleton.SUB_SINGLETON_FACTORY.get();
    } catch (IllegalStateException e) {
      System.out.println("Rightfully thrown IllegalStateException:");
      e.printStackTrace(System.out);
    }
  }
}

class SubSingleton extends Singleton {
  public static final Supplier<SubSingleton> SUB_SINGLETON_FACTORY = SubSingleton::new;

  private SubSingleton() {}
}

Требуется двойная проверка блокировки в двух экземплярах (getInstance() и конструктор), если вызов getInstance() критичен к производительности, т. Е. Вы не можете позволить себе блокировать каждый вызов для него. Кроме того, если синглтон используется по назначению, вызов getInstance() никогда не должен сбрасываться. В конструкторе нам нужна блокировка с двойной проверкой, чтобы применить свойство singleton. В getInstance() он нужен нам для предотвращения преждевременного выброса IllegalStateException s.

Видя приведенное выше доказательство концепции, становится очевидным, что необходимо проявить определенную осторожность. Существует вероятность того, что где-то в вашей программе экземпляр подкласса будет создан до того, как будет выполнен первый вызов getInstance(). Это будет означать, что любой вызов getInstance() вызовет IllegalStateException (вы в основном забросали систему). Насколько я знаю, у нас нет возможности как гибко вводить фабрику, так и предотвратить вызов конструкторов подкасс за пределами предполагаемого пути.

3 голосов
/ 22 июня 2019

Теперь основное всеобъемлющее свойство одноэлементного класса состоит в том, что вы не можете создать более одного экземпляра класса.

В вашем примере вы явно делаете несколько экземпляров SubclassSingleton. Следовательно, это не одиночка.

SubclassSingleton x = new SubclassSingleton();
SubclassSingleton y = new SubclassSingleton();
System.out.println(x == y); // false

Ваш код, который сравнивает x.getInstance() и y.getInstance(), является спорным, потому что вы даже не вызываете метод экземпляра там. Вы объявили getInstance как статический метод Singleton ...

И даже если вы вызывали методы экземпляра, тот факт, что x.someMethod() == y.someMethod() не доказывает, что x и y являются одним и тем же объектом ... или (в общем), что они эквивалентны.


Так как же это нарушает одиночность Singleton?

Simple.

Каждый экземпляр SubclassSingleton также является Singleton ... потому что SubclassSingleton расширяется Singleton. Поэтому, когда вы делаете это:

SubclassSingleton x = new SubclassSingleton();
SubclassSingleton y = new SubclassSingleton();

вы создали два отдельных Singleton объекта. Это нарушает свойство singleton-ness класса Singleton. Например:

Singleton x = new SubclassSingleton();
Singleton y = new SubclassSingleton();
System.out.println(x == y); // false
0 голосов
/ 23 июня 2019

Трудно представить, как подклассы класса Singleton будут противоречить определению шаблона Singleton, потому что запись для Singleton в Шаблоны проектирования: элементы многоразового объектно-ориентированного программного обеспечения (1994) имеет раздел под названием Подкласс класса Singleton :

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

Раздел содержит иллюстрацию того, как это можно сделать с помощью переменных среды, реестра классов Singleton и отложенной инициализации экземпляра. Однако его сложность подкрепляет высказанную в других ответах мысль о том, что, хотя это и возможно, это своего рода экзотический вариант дизайна.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...