Основное назначение паттерна Singleton — обеспечение гарантии того, что в программе будет существовать только один экземпляр класса и предоставление глобальной точки доступа к этому экземпляру. Достигается это путем запрета прямого инстанцирования экземпляров класса при помощи конструктора и описания в классе специального статического метода, отвечающего за создание и доступ к единственному экземпляру класса. В ActionScript 3 нельзя обозначить конструктор класса как private и в связи с этим становится невозможным использование «классической» реализации паттерна без дополнительной доработки ее напильником. В этой статье я хотел собрать воедино весь известный мне материал, касающийся реализации паттерна Singleton в языке ActionScript 3.
Кому лень просматривать листинги возможных вариантов реализации паттерна, и кто хочет побыстрее узнать к какому выводу я пришел, при выборе предпочтительного варианта, тот может смело крутить ползунок браузера вниз — к части «Подводим итоги» — и испортить себе тем самым всю интригу.
Я еще раз хочу напомнить вам, что Одиночка должен обеспечивать две вещи:
- Запретить создание экземпляров класса при помощи конструктора.
- Предоставлять доступ к единственному экземпляру класса при помощи статического метода или свойства, защищенного от записи.
Давайте, не забывая об этих обязанностях, рассмотрим, как они выполняются в той или иной реализации паттерна.
Вариант с созданием экземпляра на этапе инициализации статических членов класса
Это самый простой вариант, не требующий никаких дополнительных действий. Как известно, все классы приложения экспортируются в определенный кадр ролика (по умолчанию в первый) и именно в этом кадре и происходит их инициализация. Как я себе представляю последовательность: сначала создается шаблон каждого класса, затем инициализируются статические члены классов и, лишь после этого, начинают создаваться конкретные экземпляры объектов классов. Т. е. если мы создадим единственный экземпляр Singleton на этапе инициализации статических членов класса, то мы совершенно точно создадим его до того, как это будет сделано где-нибудь еще в программе. Добавим в конструктор проверку на существование единственного экземпляра и Одиночка готов:
-
package
-
{
-
/**
-
* Пример реализации паттерна Одиночка на языке ActionScript 3.
-
* с созданием экземпляра на этапе инициализации класса.
-
*
-
* @author Yuri "Barmaley" Yarovoy
-
* @version 1.0
-
*/
-
public class Singleton
-
{
-
-
// Ссылка на единственный экземпляр класса.
-
private static var __instance:Singleton = new Singleton();
-
-
/**
-
* Возвращает ссылку на единственный экземпляр класса.
-
*/
-
public static function get instance():Singleton
-
{
-
return __instance;
-
}
-
-
/**
-
* Конструктор.
-
*/
-
public function Singleton()
-
{
-
if(__instance)throw new Error("Вы не можете создавать экземпляры класса при помощи конструктора. Для доступа к экземпляру используйте Singleton.instance.");
-
}
-
}
-
}
Однако этот вариант плох тем, что не поддерживает «ленивую» инициализацию. Т. е. единственный экземпляр Одиночки создается совместно с инициализацией статических членов класса и с тех пор присутствует в программе, даже если он там еще и не нужен. Кроме того, если Одиночка имеет большое количество свойств, то это может заметно сказаться на времени запуска приложения. Вариант поддерживает использование явного геттера get instance для обеспечения доступа к единственному экземпляру класса.
Вариант с проверкой булева флага на возможность инстанцирования
-
package
-
{
-
/**
-
* Пример реализации паттерна Одиночка на языке ActionScript 3
-
* с проверкой специального флага на возможность инстанцирования.
-
*
-
* @author Yuri "Barmaley" Yarovoy
-
* @version 1.0
-
*/
-
public class Singleton
-
{
-
private static var __instance:Singleton;
-
private static var __allowInstantiation:Boolean = false;
-
-
public static function get instance():Singleton
-
{
-
if(!__instance)
-
{
-
// Разрешаем создание экземпляра класса.
-
__allowInstantiation = true;
-
// Создаем экземпляр.
-
__instance = new Singleton();
-
// Запрещаем создание экземпляров.
-
__allowInstantiation = false;
-
}
-
return __instance;
-
}
-
/**
-
* Конструктор.
-
*/
-
public function Singleton()
-
{
-
if(!__allowInstantiation)
-
throw new Error("Вы не можете создавать экземпляры класса при помощи конструктора. Для доступа к экземпляру используйте Singleton.instance.");
-
}
-
}
-
}
В этой реализации паттерна в конструкторе делается проверка приватного свойства __allowInstantiation и создание экземпляра разрешается только в том случае, если значением __allowInstantiation является true. Совершенно понятно, что в статическом методе get instance мы кратковременно делаем это свойство равным true, а потом снова запрещаем создание экземпляра, делая свойство равным false.
Вариант поддерживает использование явного геттера get instance для обеспечения доступа к единственному экземпляру класса. Минус этой реализации в том, что класс хранит еще одно дополнительное свойство — __allowInstantiation.
Вариант с использованием вложенного класса
Извиняюсь за тавтологию, но без нее никак не обойтись. В ActionScript 3 в файлe класса можно помимо основного класса описывать еще и вложенные классы. К этим классам мы можем обращаться только из кода основного класса, и они не будут видимы за пределами основного класса. Находчивые люди решили использовать эту возможность для создания Одиночки:
-
package
-
{
-
/**
-
* Пример реализации паттерна Одиночка на языке ActionScript 3
-
* с использованием вложенного класса.
-
*
-
* @author Yuri "Barmaley" Yarovoy
-
* @version 1.0
-
*/
-
public class Singleton
-
{
-
private static var __instance:Singleton;
-
-
public static function get instance():Singleton
-
{
-
if(!__instance)
-
{
-
__instance = new Singleton(new SingletonEnforcer());
-
}
-
return __instance;
-
}
-
-
/**
-
* Конструктор.
-
*
-
* @param enforcer Параметр, гарантирующий вызов конструктора только внутри класса <code>Singleton</code>,
-
* поскольку класс <code>SingletonEnforcer</code> не виден за пределами <code>Singleton</code>.
-
*/
-
public function Singleton(enforcer:SingletonEnforcer)
-
{
-
if(enforcer == null)
-
throw new Error("Вы не можете создавать экземпляры класса при помощи конструктора. Для доступа к экземпляру используйте Singleton.instance.");
-
}
-
}
-
}
-
-
// Доступ к этому вложенному классу возможен только из класса Singleton,
-
// поэтому никакой другой объект не сможет передать объект типа SingletonEnforcer в конструктор класса Singleton,
-
// а, следовательно, и создать экземпляр класса Singleton при помощи конструктора.
-
class SingletonEnforcer {}
Если честно, то со стороны этот способ выглядит как банальный хак. В памяти программы теперь будет постоянно присутствовать SingletonEnforcer — пусть и сверхлегкий (без единого свойства и метода), но все-таки настоящий класс. Но самый большой недостаток этого варианта — это то, что в сигнатуре конструктора Singleton теперь присутствует совершенно непонятный со стороны параметр enforcer. При генерации документации к проекту, утилита ASDoc так же поместит описание этого параметра в документацию, обозначив его тип как SingletonEnforcer, но вот описание класса SingletonEnforcer в документацию помещено не будет. Вариант поддерживает использование явного геттера get instance для обеспечения доступа к единственному экземпляру класса.
Вариант с проверкой функции, создающей экземпляр класса
Встречается еще и следующая реализация паттерна:
-
package
-
{
-
/**
-
* Пример реализации паттерна Одиночка на языке ActionScript 3
-
* с проверкой функции, создающей экземпляр класса.
-
*
-
* @author Yuri "Barmaley" Yarovoy
-
* @version 1.0
-
*/
-
public class Singleton
-
{
-
private static var __instance:Singleton;
-
-
public static function getInstance():Singleton
-
{
-
if(!__instance)
-
{
-
__instance = new Singleton(arguments.callee);
-
}
-
return __instance;
-
}
-
-
public function Singleton(caller:Function)
-
{
-
if(caller != Singleton.getInstance)
-
throw new Error("Вы не можете создавать экземпляры класса при помощи конструктора. Для доступа к экземпляру используйте Singleton.getInstance().");
-
}
-
}
-
}
Как мы можем заметить, это всего лишь немного модифицированный предыдущий вариант. На этот раз, в конструктор в качестве параметра передается ссылка на функцию, создающую экземпляр объекта. В конструкторе проверяется, что объект создается методом getInstance и то производится создание объекта.
Во-первых, этот вариант, так же как и предыдущий, плох тем, что у конструктора появляется параметр, не имеющий больше никакой практической ценности, кроме как определения допустимости создания экземпляра. Во-вторых, он обладает довольно слабой защитой от создания экземпляров без вызова метода getInstance. Достаточно посмотреть на описание класса и его конструктора в документации, чтобы сообразить, как эту защиту можно обойти:
-
import Singleton;
-
var s:Singleton = new Singleton(Singleton.getInstance);
Этот вариант выглядел бы гораздо привлекательней, если бы в ActionScript 3 поддерживалось свойство arguments.caller:
-
package
-
{
-
/**
-
* Красивый вариант реализации паттерна Одиночка на языке ActionScript 3,
-
* который был бы возможен, если бы в этой версии языка не было устранено свойство <code>arguments.caller</code>.
-
*
-
* @author Yuri "Barmaley" Yarovoy
-
* @version 1.0
-
*/
-
public class Singleton
-
{
-
private static var __instance:Singleton;
-
-
public static function getInstance():Singleton
-
{
-
if(!__instance)
-
{
-
__instance = new Singleton();
-
}
-
return __instance;
-
}
-
-
public function Singleton()
-
{
-
if(arguments.caller != Singleton.getInstance)
-
throw new Error("Вы не можете создавать экземпляры класса при помощи конструктора. Для доступа к экземпляру используйте Singleton.getInstance().");
-
}
-
}
-
}
Но, увы, этой возможности мы не имеем — свойство arguments.caller было устранено в AS3.
В-третьих, этот вариант реализации паттерна не позволяет использовать для доступа к единственному экземпляру класса явный геттер, поддерживаемый языком — get instance — поскольку следующая строка вызовет ошибку на этапе компиляции из-за несоответствия типов:
-
if(caller != Singleton.instance)
Подводим итоги
Когда я искал информацию по этой теме, то в комментариях одного англоязычного блога я встретил мнение, что если язык не поддерживает какие-либо конструкции, требуемые для реализации паттерна, то и не нужно ничего придумывать и обходными путями пытаться реализовать паттерн. Однако хоть реализация паттерна Singleton с приватным конструктором и является классической, однако она не навязывается ни одним сборником паттернов. Паттерны должны быть меньше всего привязаны к какому-то конкретному языку или его версии. Паттерны — это шаблоны дизайна приложения. Фактически это означает шаблон достижения в приложении того или иного функционала. А уж какую реализацию тот или иной паттерн имеет в конкретном языке — это дело второстепенное. Поэтому вывод тут должен быть только один: «Язык не поддерживает приватные конструкторы? Ищем альтернативу. Но, ни в коем случае не отказываемся от паттерна в том месте приложения, где он необходим».
Так как все из описанных в статье вариантов имеют те или иные минусы, то мы просто выберем из них один, имеющий меньшее количество недостатков. Варианты 3 и 4 с добавлением параметра в конструктор класса сразу же отметаются, так как они портят сигнатуру конструктора и могут ввести в замешательство другого разработчика, не знакомого с подобной техникой. Из оставшихся двух вариантов, вариант с инициализацией единственного экземпляра на этапе инициализации класса тоже выбывает, так как он не поддерживает ленивую инициализацию и может привести к сбоям в приложении, если ему вдруг потребуется использовать какие-то зависимости от других объектов. Остается вариант с приватным флагом, который и признается самым предпочтительным, работоспособным и безопасным. Все-таки, постоянно присутствующая в памяти булева переменная — это не такая уж и большая плата за возможность пользоваться паттерном Singleton.
Спасибо, что дочитали статью до конца и проделали все умозаключения вместе со мной. Продуктивной вам разработки! =)
См. так же:




По поводу документации к Singleton'ам. Конструктор можно исключить из документации полностью с помощью
@private: