User's collector

Внимание!   Данная опция будет доступна только после того, как вы авторизуетесь.
   запомнить меня 

Основное назначение паттерна Singleton — обеспечение гарантии того, что в программе будет существовать только один экземпляр класса и предоставление глобальной точки доступа к этому экземпляру. Достигается это путем запрета прямого инстанцирования экземпляров класса при помощи конструктора и описания в классе специального статического метода, отвечающего за создание и доступ к единственному экземпляру класса. В ActionScript 3 нельзя обозначить конструктор класса как private и в связи с этим становится невозможным использование «классической» реализации паттерна без дополнительной доработки ее напильником. В этой статье я хотел собрать воедино весь известный мне материал, касающийся реализации паттерна Singleton в языке ActionScript 3.

Кому лень просматривать листинги возможных вариантов реализации паттерна, и кто хочет побыстрее узнать к какому выводу я пришел, при выборе предпочтительного варианта, тот может смело крутить ползунок браузера вниз — к части «Подводим итоги» — и испортить себе тем самым всю интригу.

Я еще раз хочу напомнить вам, что Одиночка должен обеспечивать две вещи:

  1. Запретить создание экземпляров класса при помощи конструктора.
  2. Предоставлять доступ к единственному экземпляру класса при помощи статического метода или свойства, защищенного от записи.

Давайте, не забывая об этих обязанностях, рассмотрим, как они выполняются в той или иной реализации паттерна.

Вариант с созданием экземпляра на этапе инициализации статических членов класса

Это самый простой вариант, не требующий никаких дополнительных действий. Как известно, все классы приложения экспортируются в определенный кадр ролика (по умолчанию в первый) и именно в этом кадре и происходит их инициализация. Как я себе представляю последовательность: сначала создается шаблон каждого класса, затем инициализируются статические члены классов и, лишь после этого, начинают создаваться конкретные экземпляры объектов классов. Т. е. если мы создадим единственный экземпляр Singleton на этапе инициализации статических членов класса, то мы совершенно точно создадим его до того, как это будет сделано где-нибудь еще в программе. Добавим в конструктор проверку на существование единственного экземпляра и Одиночка готов:

Actionscript:
  1. package
  2. {
  3.     /**
  4.      * Пример реализации паттерна Одиночка на языке ActionScript 3.
  5.      * с созданием экземпляра на этапе инициализации класса.
  6.      *
  7.      * @author  Yuri "Barmaley" Yarovoy
  8.      * @version 1.0
  9.      */
  10.     public class Singleton
  11.     {
  12.  
  13.         // Ссылка на единственный экземпляр класса.
  14.         private static var __instance:Singleton = new Singleton();
  15.  
  16.         /**
  17.          * Возвращает ссылку на единственный экземпляр класса.
  18.          */
  19.         public static function get instance():Singleton
  20.         {
  21.             return __instance;
  22.         }
  23.  
  24.         /**
  25.          * Конструктор.
  26.          */
  27.         public function Singleton()
  28.         {
  29.             if(__instance)throw new Error("Вы не можете создавать экземпляры класса при помощи конструктора. Для доступа к экземпляру используйте Singleton.instance.");
  30.         }
  31.     }
  32. }

Однако этот вариант плох тем, что не поддерживает «ленивую» инициализацию. Т. е. единственный экземпляр Одиночки создается совместно с инициализацией статических членов класса и с тех пор присутствует в программе, даже если он там еще и не нужен. Кроме того, если Одиночка имеет большое количество свойств, то это может заметно сказаться на времени запуска приложения. Вариант поддерживает использование явного геттера get instance для обеспечения доступа к единственному экземпляру класса.

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

Actionscript:
  1. package
  2. {
  3.     /**
  4.      * Пример реализации паттерна Одиночка на языке ActionScript 3
  5.      * с проверкой специального флага на возможность инстанцирования.
  6.      *
  7.      * @author  Yuri "Barmaley" Yarovoy
  8.      * @version 1.0
  9.      */
  10.     public class Singleton
  11.     {
  12.         private static var __instance:Singleton;
  13.         private static var __allowInstantiation:Boolean = false;
  14.  
  15.         public static function get instance():Singleton
  16.         {
  17.             if(!__instance)
  18.             {
  19.                 // Разрешаем создание экземпляра класса.
  20.                 __allowInstantiation = true;
  21.                 // Создаем экземпляр.
  22.                 __instance = new Singleton();
  23.                 // Запрещаем создание экземпляров.
  24.                 __allowInstantiation = false;
  25.             }
  26.             return __instance;
  27.         }
  28.         /**
  29.          * Конструктор.
  30.          */
  31.         public function Singleton()
  32.         {
  33.             if(!__allowInstantiation)
  34.                 throw new Error("Вы не можете создавать экземпляры класса при помощи конструктора. Для доступа к экземпляру используйте Singleton.instance.");
  35.         }
  36.     }
  37. }

В этой реализации паттерна в конструкторе делается проверка приватного свойства __allowInstantiation и создание экземпляра разрешается только в том случае, если значением __allowInstantiation является true. Совершенно понятно, что в статическом методе get instance мы кратковременно делаем это свойство равным true, а потом снова запрещаем создание экземпляра, делая свойство равным false.

Вариант поддерживает использование явного геттера get instance для обеспечения доступа к единственному экземпляру класса. Минус этой реализации в том, что класс хранит еще одно дополнительное свойство — __allowInstantiation.

Вариант с использованием вложенного класса

Извиняюсь за тавтологию, но без нее никак не обойтись. В ActionScript 3 в файлe класса можно помимо основного класса описывать еще и вложенные классы. К этим классам мы можем обращаться только из кода основного класса, и они не будут видимы за пределами основного класса. Находчивые люди решили использовать эту возможность для создания Одиночки:

Actionscript:
  1. package
  2. {
  3.     /**
  4.      * Пример реализации паттерна Одиночка на языке ActionScript 3
  5.      * с использованием вложенного класса.
  6.      *
  7.      * @author  Yuri "Barmaley" Yarovoy
  8.      * @version 1.0
  9.      */
  10.     public class Singleton
  11.     {
  12.         private static var __instance:Singleton;
  13.  
  14.         public static function get instance():Singleton
  15.         {
  16.             if(!__instance)
  17.             {
  18.                 __instance = new Singleton(new SingletonEnforcer());
  19.             }
  20.             return __instance;
  21.         }
  22.  
  23.         /**
  24.          * Конструктор.
  25.          *
  26.          * @param   enforcer   Параметр, гарантирующий вызов конструктора только внутри класса <code>Singleton</code>,
  27.          *       поскольку класс <code>SingletonEnforcer</code> не виден за пределами <code>Singleton</code>.
  28.          */
  29.         public function Singleton(enforcer:SingletonEnforcer)
  30.         {
  31.             if(enforcer == null)
  32.                 throw new Error("Вы не можете создавать экземпляры класса при помощи конструктора. Для доступа к экземпляру используйте Singleton.instance.");
  33.         }
  34.     }
  35. }
  36.  
  37. // Доступ к этому вложенному классу возможен только из класса Singleton,
  38. // поэтому никакой другой объект не сможет передать объект типа SingletonEnforcer в конструктор класса Singleton,
  39. // а, следовательно, и создать экземпляр класса Singleton при помощи конструктора.
  40. class SingletonEnforcer {}

Дополнительный параметр в конструкторе классаЕсли честно, то со стороны этот способ выглядит как банальный хак. В памяти программы теперь будет постоянно присутствовать SingletonEnforcer — пусть и сверхлегкий (без единого свойства и метода), но все-таки настоящий класс. Но самый большой недостаток этого варианта — это то, что в сигнатуре конструктора Singleton теперь присутствует совершенно непонятный со стороны параметр enforcer. При генерации документации к проекту, утилита ASDoc так же поместит описание этого параметра в документацию, обозначив его тип как SingletonEnforcer, но вот описание класса SingletonEnforcer в документацию помещено не будет. Вариант поддерживает использование явного геттера get instance для обеспечения доступа к единственному экземпляру класса.

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

Встречается еще и следующая реализация паттерна:

Actionscript:
  1. package
  2. {
  3.     /**
  4.      * Пример реализации паттерна Одиночка на языке ActionScript 3
  5.      * с проверкой функции, создающей экземпляр класса.
  6.      *
  7.      * @author  Yuri "Barmaley" Yarovoy
  8.      * @version 1.0
  9.      */
  10.     public class Singleton
  11.     {
  12.         private static var __instance:Singleton;
  13.  
  14.         public static function getInstance():Singleton
  15.         {
  16.             if(!__instance)
  17.             {
  18.                 __instance = new Singleton(arguments.callee);
  19.             }
  20.             return __instance;
  21.         }
  22.  
  23.         public function Singleton(caller:Function)
  24.         {
  25.             if(caller != Singleton.getInstance)
  26.                 throw new Error("Вы не можете создавать экземпляры класса при помощи конструктора. Для доступа к экземпляру используйте Singleton.getInstance().");
  27.         }
  28.     }
  29. }

Как мы можем заметить, это всего лишь немного модифицированный предыдущий вариант. На этот раз, в конструктор в качестве параметра передается ссылка на функцию, создающую экземпляр объекта. В конструкторе проверяется, что объект создается методом getInstance и то производится создание объекта.

Во-первых, этот вариант, так же как и предыдущий, плох тем, что у конструктора появляется параметр, не имеющий больше никакой практической ценности, кроме как определения допустимости создания экземпляра. Во-вторых, он обладает довольно слабой защитой от создания экземпляров без вызова метода getInstance. Достаточно посмотреть на описание класса и его конструктора в документации, чтобы сообразить, как эту защиту можно обойти:

Actionscript:
  1. import Singleton;
  2. var s:Singleton = new Singleton(Singleton.getInstance);

Этот вариант выглядел бы гораздо привлекательней, если бы в ActionScript 3 поддерживалось свойство arguments.caller:

Actionscript:
  1. package
  2. {
  3.     /**
  4.      * Красивый вариант реализации паттерна Одиночка на языке ActionScript 3,
  5.      * который был бы возможен, если бы в этой версии языка не было устранено свойство <code>arguments.caller</code>.
  6.      *
  7.      * @author  Yuri "Barmaley" Yarovoy
  8.      * @version 1.0
  9.      */
  10.     public class Singleton
  11.     {
  12.         private static var __instance:Singleton;
  13.  
  14.         public static function getInstance():Singleton
  15.         {
  16.             if(!__instance)
  17.             {
  18.                 __instance = new Singleton();
  19.             }
  20.             return __instance;
  21.         }
  22.  
  23.         public function Singleton()
  24.         {
  25.             if(arguments.caller != Singleton.getInstance)
  26.                 throw new Error("Вы не можете создавать экземпляры класса при помощи конструктора. Для доступа к экземпляру используйте Singleton.getInstance().");
  27.         }
  28.     }
  29. }

Но, увы, этой возможности мы не имеем — свойство arguments.caller было устранено в AS3.

В-третьих, этот вариант реализации паттерна не позволяет использовать для доступа к единственному экземпляру класса явный геттер, поддерживаемый языком — get instance — поскольку следующая строка вызовет ошибку на этапе компиляции из-за несоответствия типов:

Actionscript:
  1. if(caller != Singleton.instance)

Подводим итоги

Когда я искал информацию по этой теме, то в комментариях одного англоязычного блога я встретил мнение, что если язык не поддерживает какие-либо конструкции, требуемые для реализации паттерна, то и не нужно ничего придумывать и обходными путями пытаться реализовать паттерн. Однако хоть реализация паттерна Singleton с приватным конструктором и является классической, однако она не навязывается ни одним сборником паттернов. Паттерны должны быть меньше всего привязаны к какому-то конкретному языку или его версии. Паттерны — это шаблоны дизайна приложения. Фактически это означает шаблон достижения в приложении того или иного функционала. А уж какую реализацию тот или иной паттерн имеет в конкретном языке — это дело второстепенное. Поэтому вывод тут должен быть только один: «Язык не поддерживает приватные конструкторы? Ищем альтернативу. Но, ни в коем случае не отказываемся от паттерна в том месте приложения, где он необходим».

Так как все из описанных в статье вариантов имеют те или иные минусы, то мы просто выберем из них один, имеющий меньшее количество недостатков. Варианты 3 и 4 с добавлением параметра в конструктор класса сразу же отметаются, так как они портят сигнатуру конструктора и могут ввести в замешательство другого разработчика, не знакомого с подобной техникой. Из оставшихся двух вариантов, вариант с инициализацией единственного экземпляра на этапе инициализации класса тоже выбывает, так как он не поддерживает ленивую инициализацию и может привести к сбоям в приложении, если ему вдруг потребуется использовать какие-то зависимости от других объектов. Остается вариант с приватным флагом, который и признается самым предпочтительным, работоспособным и безопасным. Все-таки, постоянно присутствующая в памяти булева переменная — это не такая уж и большая плата за возможность пользоваться паттерном Singleton.

Спасибо, что дочитали статью до конца и проделали все умозаключения вместе со мной. Продуктивной вам разработки! =)

См. так же:

39 комментариев

Теги:

Наверняка ведь многие задавались вопросом: «Почему бы не использовать вместо Одиночки обычный класс со статическими методами и свойствами?». В ActionScript 3 создать Singleton без дополнительных маневров не получится и бывают случаи, когда хочется попросту обойтись без них.

На самом деле есть несколько причин, выступающих за то, чтобы использовать Одиночку:

  1. В ActionScript 3 статические методы и свойства не наследуются. Т. е. если вы реализовали какой-либо функционал в виде статических членов класса, то он не будет передан по наследству подклассам класса и вам придется заново определять этот функционал в каждом из подклассов.
  2. В дальнейшем ходе развития кода вашего приложения вы можете пересмотреть политику взаимодействия объектов в нем. И вам может понадобиться уже несколько экземпляров вашего класса, каждый из которых будет хранить собственные значения свойств, обладая при этом схожим с другими экземплярами функционалом. В случае использования Одиночки это делается без особого труда — достаточно переделать статический метод getInstance так, чтобы он каждый раз возвращал новый объект класса. Есть даже специальное название для паттерна, обладающего с Singleton схожей структурой, но позволяющего создавать несколько экземпляров классов и вести их учет — Multiton. В случае же использования класса со статическими членами это сделать уже довольно сложно: пришлось бы создавать полностью одинаковые классы-дубликаты нашего класса, что само по себе является просто ужасным примером дизайна приложения.
  3. Значения статическим членам класса будут присвоены во время инициализации самого класса. На практике это означает, что, как только дойдет очередь до инициализации класса, AVM или JVM присвоит статическим свойствам этого класса необходимые значения, и с этого момента они будут постоянно висеть в памяти, занимая и растрачивая попусту ресурсы компьютера. Это может быть особенно заметно, если класс хранит достаточно увесистые объекты типа изображений, звуков или видео. Кроме того потребуется больше времени и на запуск приложения. В отличие от этого варианта, вы можете разработать ваш Singleton таким образом, чтобы он поддерживал «ленивую» инициализацию. «Ленивой» называется создания экземпляра класса и присваивание значений его свойствам только тогда, когда это необходимо на самом деле, т.е. во время первого вызова getInstance.
  4. Использование объектов — более легкий вариант для понимания и отладки. Использование статических членов класса, которые не являются частью какого-то простого объекта, а принадлежат к самому классу, означает, что ваш код не создается в определенной точке вашего приложения. В результате этого могут возникать ошибки, которые довольно сложно выявить. Главным образом это проявляется, если ваш класс должен содержать ссылки на другие объекты, которые попросту еще не будут доступны во время инициализации свойств класса.

И так, в ситуациях, когда Singleton имеет преимущества перед использованием статических членов класса, мы разобрались. Но когда же будет полезно использование обычного класса со статическими свойствами и методами?

При создании классов утилит, т. е. классов представляющих собой библиотеки связанных друг с другом статических методов и статических полей, предназначенных для обработки простых значений или выполнения простых задач, не требующих взаимодействия с другими объектами. Примерами таких классов являются Math в ActionScript 3 или java.util.Arrays в Java. Создание экземпляров таких классов являлось бы абсурдным. Так же, маловероятно, что когда-нибудь потребуется наследоваться от этих классов. И, как правило, классы утилит не содержат большого количества полей и методов, чтобы это заметно сказывалось на времени запуска приложения. Поэтому в данном случае предпочтительнее использование класса со статическими членами вместо Одиночки.

Однако не забывайте снабжать утилитный класс конструктором, выбрасывающим ошибку при попытке создания экземпляра класса, чтобы сделать невозможным инстанцирование объектов этого класса. В ActionScript 3 это делается так:

Actionscript:
  1. package
  2. {
  3.     public class Foo
  4.     {
  5.         public function Foo()
  6.         {
  7.            throw new Error("You can't create instance of Foo. All its methods and properties are static");
  8.         }
  9.     }
  10. }

В ActionScript 2 или Java используйте простой приватный конструктор.

P.S.: Обязательно прочтите статью «Работа с сookies в as3 приложениях» от Алексея «Vooparker» Аникутина, в которой как раз и приводится пример небольшого, но очень полезного утилитного класса.

См. так же:

10 комментариев

Теги: