Название и классификация паттерна

Одиночка - паттерн, порождающий объекты.

Паттерн Singleton

Назначение

Гарантирует, что у класса есть только один экземпляр, и предоставляет к нему

глобальную точку доступа.

Мотивация

Для некоторых классов важно, чтобы существовал только один экземпляр. Хотя

в системе может быть много принтеров, но возможен лишь один спулер. Должны

быть только одна файловая система и единственный оконный менеджер. В циф-

ровом фильтре может находиться только один аналого-цифровой преобразователь

(АЦП). Бухгалтерская система обслуживает только одну компанию.

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

земпляр легко доступен? Глобальная переменная дает доступ к объекту, но не за-

прещает инстанцировать класс в нескольких экземплярах.

Более удачное решение - сам класс контролирует то, что у него есть только

один экземпляр, может запретить создание дополнительных экземпляров, пере-

хватывая запросы на создание новых объектов, и он же способен предоставить

доступ к своему экземпляру. Это и есть назначение паттерна одиночка.

Применимость

Используйте паттерн одиночка, когда:

а должен быть ровно один экземпляр некоторого класса, легко доступный

всем клиентам;

а единственный экземпляр должен расширяться путем порождения подклас-

сов, и клиентам нужно иметь возможность работать с расширенным экземп-

ляром без модификации своего кода.

Структура

Участники

a Singleton- одиночка:

- определяет операцию Instance, которая позволяет клиентам получать

доступ к единственному экземпляру. Instance - это операция класса, то

есть метод класса в терминологии Smalltalk и статическая функция-член

в C++;

- может нести ответственность за создание собственного уникального эк-

земпляра.

Порождающие паттерны

Отношения

Клиенты получают доступ к экземпляру класса Singleton только через его

операцию Instance.

Результаты

У паттерна одиночка есть определенные достоинства:

Q контролируемый доступ к единственному экземпляру. Поскольку класс

Singleton инкапсулирует свой единственный экземпляр, он полностью

контролирует то, как и когда клиенты получают доступ к нему;

а уменьшение числа имен. Паттерн одиночка - шаг вперед по сравнению с гло-

бальными переменными. Он позволяет избежать засорения пространства

имен глобальными переменными, в которых хранятся уникальные экземп-

ляры;

а допускает уточнение операций и представления. От класса Singleton мож-

но порождать подклассы, а приложение легко сконфигурировать экземп-

ляром расширенного класса. Можно конкретизировать приложение экземп-

ляром того класса, который необходим во время выполнения;

а допускает переменное число экземпляров. Паттерн позволяет вам легко изме-

нить свое решение и разрешить появление более одного экземпляра класса

Singleton. Вы можете применять один и тот же подход для управления чис-

лом экземпляров, используемых в приложении. Изменить нужно будет лишь

операцию, дающую доступ к экземплярукласса Singleton;

а большая гибкость, чем у операций класса. Еще один способ реализовать функ-

циональность одиночки - использовать операции класса, то есть статичес-

кие функции-члены в C++ и методы класса в Smalltalk. Но оба этих приема

препятствуют изменению дизайна, если потребуется разрешить наличие

нескольких экземпляров класса. Кроме того, статические функции-члены

в C++ не могут быть виртуальными, так что их нельзя полиморфно замес-

тить в подклассах.

Реализация

При использовании паттерна одиночка надо рассмотреть следующие вопросы:

а гарантирование единственного экземпляра. Паттерн одиночка устроен так,

что тот единственный экземпляр, который имеется у класса, - самый обыч-

ный, но больше одного экземпляра создать не удастся. Чаще всего для этого

прячут операцию, создающую экземпляры, за операцией класса (то есть за

статической функцией-членом или методом класса), которая гарантирует

создание не более одного экземпляра. Данная операция имеет доступ к пе-

ременной, где хранится уникальный экземпляр, и гарантирует инициализа-

цию переменной этим экземпляром перед возвратом ее клиенту. При таком

подходе можно не сомневаться,что одиночка будет создан и инициализи-

рован перед первым использованием.

В C++ операция класса определяется с помощью статической функции-чле-

на Instance класса Singleton. В этом классе есть также статическая

Паттерн Singleton Jill

переменная-член „instance, которая содержит указатель на уникальный

экземпляр.

Класс Singleton объявлен следующим образом:

class Singleton {

public:

static Singleton* Instance();

protected:

Singleton();

private:

static Singleton* „instance;

};

А реализация такова:

Singleton* Singleton::_instance = 0;

Singleton* Singleton::Instance () {

if (_instance == 0) {

_instance = new Singleton;

}

return „instance;

}

Клиенты осуществляют доступ к одиночке исключительно через функцию-

член Instance. Переменная „instance инициализируется нулем, а ста-

тическая функция-член Instance возвращает ее значение, инициализируя

ее уникальным экземпляром, если в текущий момент оно равно 0. Функция

Instance использует отложенную инициализацию: возвращаемое ей зна-

чение не создается и не хранится вплоть до момента первого обращения.

Обратите внимание, что конструктор защищенный. Клиент, который попы-

тается инстанцировать класс Singleton непосредственно, получит ошиб-

ку на этапе компиляции. Это дает гарантию, что будет создан только один

экземпляр.

Далее, поскольку „instance - указатель на объект класса Singleton, то

функция-член Instance может присвоить этой переменной указатель на

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

в разделе ≪Пример кода≫.

О реализации в C++ скажем особо. Недостаточно определить рассматрива-

емый патерн как глобальный или статический объект, а затем полагаться на

автоматическую инициализацию. Тому есть три причины:

- мы не можем гарантировать, что будет объявлен только один экземпляр

статического объекта;

- у нас может не быть достаточно информации для инстанцирования лю-

бого одиночки во время статической инициализации. Одиночке могут

быть необходимы данные, вычисляемые позже, во время выполнения

программы;

- в C++ не определяется порядок вызова конструкторов для глобальных

объектов через границы единиц трансляции [ES90]. Это означает, что

Порождающие паттерны

между одиночками не может существовать никаких зависимостей. Если

они есть, то ошибок не избежать.

Еще один (хотя и не слишком серьезный) недостаток глобальных/статических

объектов в том, что приходится создавать всех одиночек, даже, если они не

используются. Применение статической функции-члена решает эту проблему.

В Smalltalk функция, возвращающая уникальный экземпляр, реализуется

как метод класса Singleton. Чтобы гарантировать единственность экземп-

ляра, следует заместить операциюnew. Получающийся класс мог бы иметь

два метода класса (в них Solelnstance - это переменная класса, которая

больше нигде не используется):

new

self error: 'не удается создать новый объект1

default

Solelnstance isNil ifTrue: [Solelnstance := super new].

^ Solelnstance

а порождение подклассов Singleton. Основной вопрос не столько в том, как опре-

делить подкласс, а в том, как сделать, чтобы клиенты могли использовать

его единственный экземпляр. По существу, переменная, ссылающаяся на

экземпляр одиночки, должна инициализироваться вместе с экземпляром

подкласса. Простейший способ добиться этого - определить одиночку, ко-

торого нужно применять в операции Instance класса Singleton. В раз-

деле ≪Пример кода≫ показывается, как можно реализовать эту технику с по-

мощью переменных среды.

Другой способ выбора подкласса Singleton - вынести реализацию опера-

ции Instance из родительского класса (например, MazeFactory) и помес-

тить ее в подкласс. Это позволит программисту на C++ задать класс оди-

ночки на этапе компоновки (скомпоновав программу с объектным файлом,

содержащим другую реализацию), но от клиента одиночка будет по-прежне-

му скрыт.

Такой подход фиксирует выбор класса одиночки на этапе компоновки, за-

трудняя тем самым его подмену во время выполнения. Применение услов-

ных операторов для выбора подкласса увеличивает гибкость решения, но

все равно множество возможных классов Singleton остается жестко ≪за-

шитым≫ в код. В общем случае ни тот, ни другой подход не обеспечивают

достаточной гибкости.

Ее можно добиться за счет использования реестра одиночек. Вместо того

чтобы задавать множество возможных классов Singleton в операции

Instance, одиночки могут регистрировать себя по имени в некотором всем

известном реестре.

Реестр сопоставляет одиночкам строковые имена. Когда операции Instance

нужен некоторый одиночка, она запрашивает его у реестра по имени. Начи-

нается поиск указанного одиночки, и, если он существует, реестр возвраща-

ет его. Такой подход освобождает Instance от необходимости ≪знать≫ все

Паттерн Singleton

возможные классы или экземпляры Singleton. Нужен лишь единый для

всех классов Singleton интерфейс, включающий операции с реестром:

class Singleton {

public:

static void Register(const char* name, Singleton*);

static Singleton* Instance ().;

protected:

static Singleton* Lookup(const char* name);

private:

static Singleton* „instance;

static List<NameSingletonPair>* „registry;

Операция Register регистрирует экземпляр класса Singleton под ука-

занным именем. Чтобы не усложнять реестр, мы будем хранить в нем спи-

сок объектов NameSingletonPair. Каждый такой объект отображает имя

на одиночку. Операция Lookup ищет одиночку по имени. Предположим, что

имя нужного одиночки передается в переменной среды:

Singleton* Singleton::Instance () {

if („instance == 0) {

const char* singletonName = getenv("SINGLETON");

// пользователь или среда предоставляют это имя на стадии

// запуска программы

_instance = Lookup(singletonName);

// Lookup возвращает 0, если такой одиночка не найден

return „instance;

В какой момент классы Singleton регистрируют себя? Одна из возмож-

ностей - конструктор. Например, подкласс MySingleton мог бы работать

так:

MySingleton::MySingleton() {

Singleton::Register("MySingleton", this);

}

Разумеется, конструктор не будет вызван, пока кто-то не инстанцирует

класс, но ведь это та самая проблема, которую паттерн одиночка и пытается

разрешить! В C++ ее можно попытаться обойти, определив статический эк-

земпляр класса My Single ton. Например, можно вставить строку

static MySingleton theSingleton;

в файл, где находится реализация MySingleton.

Теперь класс Singleton не отвечает за создание одиночки. Его основной

обязанностью становится обеспечение доступа к объекту-одиночке из

Порождающие паттерны

любой части системы. Подход, сводящийся к применению статического

объекта, по-прежнему имеет потенциальный недостаток: необходимо созда-

вать экземпляры всех возможных подклассов Singleton, иначе они не бу-

дут зарегистрированы.

Пример кода

Предположим, нам надо определить класс MazeFactory для создания лаби-

ринтов, описанный на стр. 99. MazeFactory определяет интерфейс для построения

различных частей лабиринта. В подклассах эти операции могут переопределять-

ся, чтобы возвращать экземпляры специализированных классов продуктов, на-

пример объекты BombedWall, а не просто Wall.

Существенно здесь то, что приложению Maze нужен лишь один экземпляр

фабрики лабиринтов и он должен быть доступен в коде, строящем любую часть

лабиринта. Тут-то паттерн одиночка и приходит на помощь. Сделав фабрику

MazeFactory одиночкой, мы сможем обеспечить глобальную доступность объек-

та, представляющего лабиринт, не прибегая к глобальным переменным.

Для простоты предположим, что мы никогда не порождаем подклассов от

MazeFactory. (Чуть ниже будет рассмотрен альтернативный подход.) В C++ для

того, чтобы превратить фабрику в одиночку, мы добавляем в класс MazeFactory

статическую операцию Instance и статический член _instance, в котором бу-

дет храниться единственный экземпляр. Нужно также сделать конструктор защи-

щенным, чтобы предотвратить случайное инстанцирование, в результате которо-

го будет создан лишний экземпляр:

class MazeFactory {

public:

static MazeFactory* Instance();

// здесь находится существующий интерфейс

protected:

MazeFactory();

private:

static MazeFactory* „instance;

};

Реализация класса такова:

MazeFactory* MazeFactory::_instance = 0;

MazeFactory* MazeFactory::Instance 0 {

if (_instance == 0) {

_instance = new MazeFactory;

}

return _instance;

}

Теперь посмотрим, что случится, когда у MazeFac tory есть подклассы и опре-

деляется, какой из них использовать. Вид лабиринта мы будем выбирать с по-

мощью переменной среды, поэтому добавим код, которыйинстанцирует нужный

Паттерн Singleton

подкласс MazeFactory в зависимости от значения данной переменной. Лучше

всего поместить код в операцию Instance, поскольку она уже и так инстанциру-

ет MazeFactory:

MazeFactory* MazeFactory::Instance () {

if (_instance == 0) {

const char* mazeStyle = getenv("MAZESTYLE");

if (strcmp(mazeStyle, "bombed") == 0) {

„.instance = new BombedMazeFactory;

} else if (strcmp(mazeStyle, "enchanted") == 0) {

_instance = new EnchantedMazeFactory;

// ... другие возможные подклассы

} else { // по умолчанию

_instance = new MazeFactory;

}

}

return _instance;

}

Отметим, что операцию Instance нужно модифицировать при определении

каждого нового подкласса MazeFactory. В данном приложении это, может быть,

и не проблема, но для абстрактных фабрик, определенных в каркасе, такой под-

ход трудно назвать приемлемым.

Одно из решений - воспользоваться принципом реестра, описанным в разде-

ле ≪Реализация≫. Может помочь и динамическое связывание, тогда приложению

не нужно будет загружать все неиспользуемые подклассы.

Известные применения

Примером паттерна одиночка в Smalltalk-80 [РагЭО] является множество из-

менений кода, представленное классом ChangeSet. Более тонкий пример - это

отношение между классами и их метаклассами. Метаклассом называется класс

класса, каждый метакласс существует в единственном экземпляре. У метакласса

нет имени (разве что косвенное, определяемое экземпляром), но он контролирует

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

В библиотеке Interviews для создания пользовательских интерфейсов

[LCI+92] - паттерн одиночка применяется для доступа к единственным экземпля-

рам классов Session (сессия) и WidgetKit (набор виджетов). Классом Session

определяется главный цикл распределения событий в приложении. Он хранит

пользовательские настройки стиля и управляет подключением к одному или не-

скольким физическим дисплеям. WidgetKit - это абстрактная фабрика для

определения внешнего облика интерфейсных виджетов. Операция Widget-

Kit: : instance () определяет конкретный инстанцируемый подкласс WidgetKit

на основе переменной среды, которую устанавливает Session. Аналогичная опе-

рация в классе Session ≪выясняет≫, поддерживаются ли монохромные или цвет-

ные дисплеи, и соответственно конфигурирует одиночку Session.

Порождающие паттерны

Родственные паттерны

С помощью паттерна одиночка могут быть реализованы многие паттерны. См.

описание абстрактной фабрики, строителя и прототипа.