суббота, 12 августа 2017 г.

Синглтон: глубокое погружение

Сингтон - паттерн проектирования, который используется, чтобы контролировать число создаваемых объектов, слодовательно он относится семейству паттернов Создатели.
Была тенденция создавать максимум один объект, но в некоторых ситуациях необходимо создать фиксированное число объектов; и именно в этом нам поможет данный паттерн. Обычно, конструктор создается приватным, чтобы убедиться, что внешний мир не может создать объекты и предоставляем один статический метод, который возвращает объект. Создание объекта происходит только если он не был создан до этого.
Со временем люди осознали несколькоп роблем с обычной реализацией синглтона и он был улучшен, чтобы решить эти проблемы. Запомните, что синглтон не хороший или плохой, он просто может подходить для решения вышей проблемы.
В этой статье мы рассмотрим различные реализации синглтонов.
Взглянем на диаграмму класса:

Применение синглтона

Есть несколько ситуаций, когда целесобразно использовать паттерн синглтон. На пример: логирование, кэширование, балансировка нагрузки, конфигурирование, коммуникации и пулл-коннекшинов в БД. Пример использования синглтона в Java API - класс Runtime.

Реализация синглтона

Приведем пример простой реализации синглтона. Инициализация произойдет при запуске приожения, так же данный способ является потокобезопасный.

public class MySingleton {
     private static final MySingleton mySingleton = new MySingleton();
     private MySingleton(){}     
     public static MySingleton getInstance(){
        return mySingleton;
     }
}
А вот реализация использующая инициализацию при первом обращении. В многопоточном приложении данная реализация будет плогих решением:

class MySingleton {
     private static MySingleton mySingleton;
     private MySingleton(){}
     public static MySingleton getInstance(){
           if(null == mySingleton) {
                 mySingleton = new MySingleton();
           }
           return mySingleton;
     }
}
Решение для многопоточной среды должо избежать гонки состояний  и убедиться, что не будет нарушения философии синглтона. Но в примере ниже, делать весь метод synchronized - не лучший подход, т.к нам необходимо добавить ограничение только на создание объекта.

class MySingleton {
     private static MySingleton mySingleton;
     private MySingleton(){}
     public synchronized static MySingleton getInstance(){      
        if(null == mySingleton) {
           mySingleton = new MySingleton();
        }
        return mySingleton;
     }
}
Пример ниже делает все то же, но так же пользуется двойной проверкой, используя блокировку по объекту. Это реализация гарантирует потокобезопасность. Но дополнительный объект, который нужен только для блокировки - не лушчая практика.

class MySingleton {
  private static MySingleton mySingleton;
  private static final Object lock = new Object();
  private MySingleton(){}
  public static MySingleton getInstance(){
     if(null == mySingleton) {
         synchronized(lock) {
            if(null == mySingleton) {
               mySingleton = new MySingleton();
            }
         }   
     }
     return mySingleton;
  }
}
Другая многопоточная реализация,  пользуется двойной проверкой, но с блокировкой на уровне класса. Отметив объект MySingletone, как volatile, мы уверены, что изменения , сделанные в одном потоке, будут видны другим потокам. Данная реализация гарантирует потокобезопасность.

class MySingleton {
    private volatile static MySingleton mySingleton;
    private MySingleton() {}
    public static MySingleton getInstance() {
        if (null == mySingleton) {
            synchronized(MySingleton.class) {
                if (null == mySingleton) {
                    mySingleton = new MySingleton();
                }
            }
        }
        return mySingleton;
    }
}
Данная реализация предоставляет конструктор, который помешает нарушить контракт синглтона, используя рефлексию.

class MySingleton {
    private volatile static MySingleton mySingleton;
    //Reflection может получить доступ к приватному конструктору
    private MySingleton() throws Exception {
        if (null == mySingleton) {
            mySingleton = new MySingleton();
        } else {
            throw new Exception("Это singleton; не ожидается создание более одного экземпляра");
        }
    }
    public static MySingleton getInstance() throws Exception {
        if (null == mySingleton) {
            synchronized(MySingleton.class) {
                if (null == mySingleton) {
                    mySingleton = new MySingleton();
                }
            }
        }
        return mySingleton;
    }
}
Очень популярная реализация, с использованием статического класса, которая предоставляет ленивую инициализацию (при первом обращении) и потокобезопасность.

public class MySingleton {
    private MySingleton() {}
    private static class SingletonUisngInner {
        private static MySingleton mySingleton = new MySingleton();
    }
    public static MySingleton getInstance() {
        return SingletonUisngInner.mySingleton;
    }
}
В некоторых обстоятельствах, если ваш синглтон наследуется от Cloneable - вашему классу необходимы дополнительные проверки, чтобы избежать нарушения контракта синглтона. Вам необходимо переопределить методо clone  и явно выкинуть ошибку CloneNotSupportedException.

class ClonedClass implements Cloneable {
    //Некоторая логика
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class MySingleton extends ClonedClass {
    private MySingleton() {}
    private static class SingletonUisngInner {
        private static MySingleton mySingleton = new MySingleton();
    }
    public static MySingleton getInstance() {
        return SingletonUisngInner.mySingleton;
    }
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
}
Следующий и наш финальный, очень популярный и умный способ реализации синглтона - использовать Enum, который возьмет все заботы на себя.

public enum  EnumSingleton{
    INSTANCE;
}
Иногда, люди говорят о синглтонах в нескольких JVM - разберемся с этим. Синглтон означет, что есть только один объект, и мы хоршо знаем, что жизненным циклом объекта управляет JVM, так что один разделяемый объект, среди нескольких JVM невозможен.
Но если вам это необходимо, вы можете создать объект в одной JVM и передавать его через сериализацию, в другие JVM (но помните, что вы десириализуете его, так что, все что отмечено static или transient не передается, и где то контракт синглтона может быть нарушен). Вы так же можете использовать RMI объекты, как синглтоны, чтобы удовлетворить вашим потребностям.
Веселого обучения!
PS это мой перевод данной статьи

пятница, 11 августа 2017 г.

Что такого плохого в Синглтонах

Все мы сталкивались со словом 'Синглтон', и большинство из нас даже знает его значение. Со слов Википедии: Синглтоны - классы, у которых есть ограничение: иметь только один экземпляр. Возможно несколько счастливчиков пользовались им, но поверьте мне, даже если вы использовали синглтон, вы можете не знать насколько большое это зло.

Чтобы было более понятно, этот паттерн содержит класс, который отвечает за создание объекта, а так же удостоверяется, что создается только один объект.

class Singleton {
    private static Singleton INSTANCE = null;
    private String criticalData;
    private Singleton() {
        criticalData = "Это должно быть уникальным, а состояние универсальным";
    }
    synchronized static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
    public String getString() {
        return this.criticalData;
    }
    public void setCriticalData(String value) {
        criticalData = value;
    }
}
Теперь мы знаем, что из мебя представляе класс синглтон и как он  работает. Итак, в чем проблема? Подсказка: самая очевидная проблема находится в 7 строке.
Т.к. блок synchronized может быть доступен только одному потоку, это может создать узкое место при получении объекта. Но есть и другие менее известные проблемы.

Синглтоны и состояние

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

Создается один объект

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

Сингтоны предоставляют глобальное состояние

Именно поэтому у нас есть синглтоны. Да. Но какой ценой? (Глобальные переменные были плохими! Помните?) Это обеспечивает возможность для некоторого сервиса в вашем приложении, так что нам не нужно передавать ссылку на этот сервис. Желание создать что-то глобальное, чтобы избежать его распространение, - это запашок от вашего дизайна.

PS это мой перевод данной статьи