Читайте также:
|
|
возможные классы или экземпляры 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; I 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; } j
return _instance; }
Отметим, что операцию Instance нужно модифицировать при определении каждого нового подкласса MazeFactory. В данном приложении это, может быть, и не проблема, но для абстрактных фабрик, определенных в каркасе, такой подход трудно назвать приемлемым.
Одно из решений - воспользоваться принципом реестра, описанным в разделе «Реализация». Может помочь и динамическое связывание, тогда приложению не нужно будет загружать все неиспользуемые подклассы.
Известные применения
Примером паттерна одиночка в Smalltalk-80 [РагЭО] является множество изменений кода, представленное классом Change Set. Более тонкий пример - это отношение между классами и их метаклассами. Метаклассом называется класс класса, каждый метакласс существует в единственном экземпляре. У метакласса нет имени (разве что косвенное, определяемое экземпляром), но он контролирует свой уникальный экземпляр, и создать второй обычно не разрешается.
В библиотеке Interviews для создания пользовательских интерфейсов [LCI+92] - паттерн одиночка применяется для доступа к единственным экземплярам классов Session (сессия) и WidgetKit (набор виджетов). Классом Session определяется главный цикл распределения событий в приложении. Он хранит пользовательские настройки стиля и управляет подключением к одному или нескольким физическим дисплеям. WidgetKit - это абстрактная фабрика для определения внешнего облика интерфейсных виджетов. Операция WidgetKit:: instance () определяет конкретный инстанцируемый подкласс WidgetKit на основе переменной среды, которую устанавливает Session. Аналогичная операция в классе Session «выясняет», поддерживаются ли монохромные или цветные дисплеи, и соответственно конфигурирует одиночку Session.
Дата добавления: 2015-09-11; просмотров: 155 | Поможем написать вашу работу | Нарушение авторских прав |