Паттерн Singleton - Одиночка

Паттерн Singleton - Одиночка

m.vk.com

П Паттерн Singleton - Одиночка

CODE BLOG | Программирование·15 янв в 22:57

Паттерны проектирования - это описание некоторых проблем, возникающих во время объектно-ориентированного проектирования, а также способов их решения (как практических, так и теоретических). Иными словами - это примеры правильных подходов к решению типичных задач проектирования.

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

П Простая реализация Singleton

Один из самых простых способов реализовать паттерн Singleton на языке Java выглядит так:

public final class Singleton {
    private static Singleton _instance = null;

    private Singleton() {}

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

Теперь приведу некоторые объяснения по поводу реализации шаблона.

Конструктор класса необходимо объявить с модификатором видимости private. Это предотвратит создание экземпляров класса как с помощью класса Singleton, так и с помощью его наследников. В связи с этим к объявлению класса смело можно дописать модификатор final.

Метод getInstance() создаст ровно один экземпляр класса Singleton. Этот метод объявлен как synchronized. Сделано это вот почему. В многопоточных программах при одновременном вызове метода getInstance() из нескольких потоков можно создать несколько экземпляров класса Singleton. А должен остаться только один!

От модификатора synchronized можно избавиться. Для этого _instance нужно проинициализировать:

private static final Singleton _instance = new Singleton(),

а в методе getInstance() убрать конструкцию "if". Тогда инициализация произойдет во время загрузки класса.

Но использование поздней инициализации (lazy initialization) предпочтительнее в случае, если создание экземпляра класса занимает много времени. Да и в случае ленивой инициализации есть возможность обработать возникшие исключитальные ситуации при вызове конструктора.

С Синглтон с double-checked locking

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

Самый распространенный способ избавиться от лишней синхронизации - это double-checked locking, который выглядит таким образом:

public final class Singleton {
    private static volatile Singleton _instance = null;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (_instance == null)
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        return _instance;
    }
}

С Синглтон с Instance Holder

А вот еще один заслуживающий внимания вариант реализации шаблона Одиночка с ленивой инициализацией:

public final class Singleton {
    private Singleton() {}

    private static class Holder {
        private static final Singleton _instance = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder._instance;
    }
}

Объект будет проинициализирован при первом вызове метода getInstance(). То есть мы перенесли проблему с синхронизацией на уровень загрузчика классов (class loader).

Но что если в приложении несколько Class Loader'ов или вообще у нас распределенная система с несколькими виртуальнми машинами Java? Тогда все сложно. Поговорим об этом в другой раз.

А вообще сейчас можно говорить, что реально крутой вариант паттерна Одиночка выглядит так:

    public enum Singleton {
        INSTANCE;
    }

Вот собственно и все... Ах, да! Зачем нам это нужно.

П Практический пример синглтона

Мне приходится чаще всего использовать этот паттерн при работе с конфигурацией. Иногда конфигурацию программы удобно хранить в файле. Допустим, это будет простой текстовый файл "props.txt" со строками типа "ключ=значение". Нам нужно гарантировать, что конфигурация в программе будет в единственном экземпляре. Вторую мы бы и так не создали, но нужно запретить это делать пользователю класса. Итак,

import java.util.*;
import java.io.*;

public class Configuration {
    private static Configuration _instance = null;

    private Properties props = null;

    private Configuration() {
         props = new Properties();
     try {
     FileInputStream fis = new FileInputStream(
                    new File("props.txt"));
     props.load(fis);
     }
     catch (Exception e) {
         // обработайте ошибку чтения конфигурации
     }
    }

    public synchronized static Configuration getInstance() {
        if (_instance == null)
            _instance = new Configuration();
        return _instance;
    }

    // получить значение свойства по имени
    public synchronized String getProperty(String key) {
        String value = null;
        if (props.containsKey(key))
            value = (String) props.get(key);
        else {
            // сообщите о том, что свойство не найдено
        }
        return value;
    }
}

Теперь для работы с конфигурацией можно использовать конструкцию вида:

String propValue = Configuration.getInstance().getProperty(propKey).

Если имена свойств в "props.txt" меняться не будут, можно описать их в классе таким образом:

public static final String PROP_KEY = "propKey",

а значения получать так:

String propValue = Configuration.getInstance()
    .getProperty(Configuration.PROP_KEY).

Паттерн Singleton полезен не только при работе с конфигурациями. Его можно использовать также при написании ConnectionPool, Factory и других вещей.

Также рекомендую ознакомиться с другими материалами посвященными

Java и

паттернам проектирования. Поставь лайк и поделись статьей с друзьями — это лучший способ помочь развитию сообщества.

Report Page