PDA

Просмотр полной версии : Синглтон в AS3


ZergMaster
26.11.2017, 17:14
у меня для этой задачи служит такой классик:

package src.utils
{
import com.greensock.TweenLite;
import flash.display.DisplayObjectContainer;
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.Dictionary;
import flash.utils.clearTimeout;
import flash.utils.setTimeout;

public class BubbleText
{
private static const BUBBLE_COLOR:uint = 0xffffcc;
private static const TIMER_TIME:uint = 700;

private var _bubbleTimeout:uint;

private static var _instance:BubbleText;
private static var _stage:DisplayObjectContainer;

private var _bubbleObjects:Dictionary = new Dictionary();

private var _textDescription:TextField = new TextField();;
private var _textSprite:Sprite = new Sprite();

public function BubbleText(itIsSingleton:PrivateSingleton)
{
_textSprite.addChild(_textDescription);
}

public static function gi():BubbleText
{
if (!_instance)
_instance = new BubbleText(new PrivateSingleton());

return _instance;
}

public static function init(s:DisplayObjectContainer):void
{
_stage = s;
}

public function registerObject(object:Sprite, description:String):void
{
_bubbleObjects[object] = {target:object, description:description};

if (!_stage.hasEventListener(MouseEvent.MOUSE_OVER))
{
_stage.addEventListener(MouseEvent.MOUSE_OVER, mouseOverOutHandler, true);
_stage.addEventListener(MouseEvent.MOUSE_OUT, mouseOverOutHandler, true);
}
}

private function mouseOverOutHandler(event:MouseEvent):void
{
var target:Sprite;

var check_mc:InteractiveObject = event.target as InteractiveObject;
while (check_mc != event.currentTarget as InteractiveObject)
{
var bubbleObject:Object = _bubbleObjects[check_mc as Sprite];
if (bubbleObject)
{
target = bubbleObject.target;
break;
}

check_mc = check_mc.parent;
}

if (!target) return;

switch(event.type)
{
case MouseEvent.MOUSE_OVER:
target.addEventListener(MouseEvent.MOUSE_MOVE, mouseOverOutHandler);
_bubbleTimeout = setTimeout(displayBubble, TIMER_TIME, target);
break;

case MouseEvent.MOUSE_MOVE:
removeBubble();
_bubbleTimeout = setTimeout(displayBubble, TIMER_TIME, target);
break;

case MouseEvent.MOUSE_OUT:
removeBubble();
target.removeEventListener(MouseEvent.MOUSE_MOVE, mouseOverOutHandler);
break;
}
}

private function removeBubble():void
{
clearTimeout(_bubbleTimeout);

_textSprite.graphics.clear();
_textDescription.text = '';

if(_stage.getChildByName(_textSprite.name))
_stage.removeChild(_textSprite);
}


private function displayBubble():void
{
var target:Sprite = arguments[0];

_textDescription.defaultTextFormat = new TextFormat('Verdana');
_textDescription.autoSize = TextFieldAutoSize.CENTER;
_textDescription.text = _bubbleObjects[target].description;
_textDescription.x = 0;
_textDescription.y = 0;

var gap:uint = 10;
_textSprite.graphics.lineStyle(0, 0, 1, true);
_textSprite.graphics.beginFill(BUBBLE_COLOR);
_textSprite.graphics.drawRoundRect(_textDescription.x-gap, _textDescription.y-gap, _textDescription.width+gap*2, _textDescription.height+gap*2, 10);

_textSprite.x = _stage.mouseX;
_textSprite.y = _stage.mouseY + 40;

if ((_textSprite.x + _textSprite.width) > _stage.stage.stageWidth)
_textSprite.x = _stage.mouseX - _textSprite.width + 30

if ((_textSprite.y + _textSprite.height) > _stage.stage.stageHeight)
_textSprite.y = _stage.mouseY - _textSprite.height// + 30

_stage.addChild(_textSprite);
_textSprite.alpha = 0;

TweenLite.to(_textSprite, 0.5, {alpha:1});
}
}
}

class PrivateSingleton
{
public function PrivateSingleton() {}
}

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

undefined
26.11.2017, 17:31
а зачем PrivateSingleton нужен?

ZergMaster
26.11.2017, 22:04
undefined
это реализация синглтона такая. Чтобы экземпляр можно было создать только изнутри класса (из функции gi()). Хотя да, есть возможность это обойти, добавив в тот же package класс с таким же именем... но это уже стараться надо.

caseyryan
27.11.2017, 06:32
Хотя да, есть возможность это обойти, добавив в тот же package класс с таким же именем... но это уже стараться надо.
Ты пробовал так сделать?

А что мешает сделать синглтон вот так?

package {
public class Singletone {

private static var _instance:Singleton = null;

pubic function Singltone() {
if (_instance) throw new Error("Singltone! Use Singltone.instance getter instead");
_instance = this;
}

public static function get instance():Singltone {
return _instance || new Singltone();
}
}
}


Зачем городить какие-то приватные ключи и другие конструкции?

ZergMaster
27.11.2017, 08:55
caseyryan
ну, получается. что данная конструкция, которую ты предлагаешь, не защищает от создания конструктора из вне класса. Смысл то в этом.

undefined
27.11.2017, 11:55
ZergMaster, общепринятая практика создания синглтонов - приватный конструктор и статический метод, который его дергает и возвращает инстанс.
В твоем коде назначение аргумента itIsSingleton в BubbleText совершенно не ясно т.к. он сам, судя по всему, является синглтоном(почему конструктор тогда public?) и еще зачем-то принимает синглтон на входе.Зачем?Почему?

ZergMaster
27.11.2017, 12:15
ZergMaster, общепринятая практика создания синглтонов - приватный конструктор и статический метод, который его его дергает и возвращает инстанс.
Все верно. Но приватным конструктор быть не может в as3. В данном же случае, если мы попытаемся создать экземпляр BubbleText откуда-то извне, кроме как из статического метода gi(), то у нас ничего не получится, так как туда нужно будет передать PrivateSingleton, который находится внутри документкласса BubbleText. PrivateSingleton видно только внутри документкласса BubbleText, поэтому при создании экземпляра из функции gi() проблем не будет.
А название такое, чтобы если вдруг я бы попытался создать экземпляр BubbleText где-нибудь в коде, я бы увидел, что ему нужно передать некий PrivateSingleton. Я согласен, что название, быть может, не самое удачное. Лучше назвать PrivateFromSingleton. Само же название параметра "itIsSingleton" как бы намекает, что мы имеем дело с данным паттерном и не нужно создавать его экземпляр, а искать статический метод gi()

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

caseyryan
27.11.2017, 15:58
ну, получается. что данная конструкция, которую ты предлагаешь, не защищает от создания конструктора из вне класса. Смысл то в этом.
Позволяет, но в любом случае экземпляр будет только один, что через instance, что просто вызовом конструктора в первый раз. Как раз в этом и есть главный смысл синглтона
вот в java возможно создать приватный конструктор и поэтому там таких извращений не требуется...
Джава сама - одно сплошное извращение)

ZergMaster
27.11.2017, 16:39
в любом случае экземпляр будет только один, что через instance, что просто вызовом конструктора в первый раз
это да. Не нравится мне так, непорядок. Я не гонюсь за коротким кодом с некоторых пор... ) А вот аккуратненько все должно быть и по полочкам - это желательно по возможности.
Если уж на то пошло, тогда мне больше нравится такой вариант:


package {

public class MySingleton {

private static var _instance:MySingleton;
//Переменная доступа
private static var _isConstructing:Boolean;

public function MySingleton() {
if (!_isConstructing) throw new Error("Singleton, use MySingleton.instance");
}

public static function get instance():MySingleton {
if (_instance == null) {
_isConstructing = true;
_instance = new MySingleton();
_isConstructing = false;
}
return _instance;
}
}
}


Хоть, как завещал дедушка Мартин, это и не тру, конечно, пользоваться флагами, но зато тут не создать, опять же, экземпляр из вне.

Джава сама - одно сплошное извращение)
А что такое? Детская травма?)

undefined
27.11.2017, 16:53
_isConstructing = true;
_instance = new MySingleton();
_isConstructing = false;
Это что защита для многопоточности? :)

Appleman
27.11.2017, 16:56
Хоть, как завещал дедушка Мартин, это и не тру, конечно, пользоваться флагами, но зато тут не создать, опять же, экземпляр из вне.

Поясните для чайника, плиз, что это за ситуация создания экземпляра извне? Мне представлялось, что откуда бы не создавался экземпляр, нельзя не запустить его конструктор. Или я ошибаюсь?

И ещё вопрос по теме для всех. Какой кайф создавать синглтоны и инстансы, когда можно обращаться непосредственно к паблик методам статического класса из любого места программы? Что мешает написать 'Имя класса'.'имя публичного статического метода'() и получить всё, что нужно?

undefined
27.11.2017, 17:02
статичный класс будет жить все время, пока работает приложение, а синглтоны можно создавать и удалять когда угодно.
Поясните для чайника, плиз, что это за ситуация создания экземпляра извне? Мне представлялось, что откуда бы не создавался экземпляр, нельзя не запустить его конструктор. Или я ошибаюсь?
Имеется в виду извне класса синглтона.Каноническая реализация сама создает экземпляр себя и предоставляет его всем желающим.Повторное создание со стороны блокируется модификатором private у конструктора синглтона.

Wolsh
27.11.2017, 17:08
Класс не может диспатчить события.

caseyryan
27.11.2017, 17:15
это да. Не нравится мне так, непорядок. Я не гонюсь за коротким кодом с некоторых пор... ) А вот аккуратненько все должно быть и по полочкам - это желательно по возможности.
Если уж на то пошло, тогда мне больше нравится такой вариант:


package {

public class MySingleton {

private static var _instance:MySingleton;
//Переменная доступа
private static var _isConstructing:Boolean;

public function MySingleton() {
if (!_isConstructing) throw new Error("Singleton, use MySingleton.instance");
}

public static function get instance():MySingleton {
if (_instance == null) {
_isConstructing = true;
_instance = new MySingleton();
_isConstructing = false;
}
return _instance;
}
}
}


Хоть, как завещал дедушка Мартин, это и не тру, конечно, пользоваться флагами, но зато тут не создать, опять же, экземпляр из вне.


А что такое? Детская травма?)

Ну тогда этот вариант тоже надо доделать. Потому что сейчас он позволяет сделать так:

MySingleton.instance;
new MySingleton();
new MySingleton();
new MySingleton();


Если дополнить конструктор вот так:

public function MySingleton() {
if (!_isConstructing || _instance) throw new Error("Singleton, use MySingleton.instance");
_instance = this;
}

То будет уже лучше

ZergMaster
27.11.2017, 17:19
Это что защита для многопоточности? :)
Это как раз приватизация конструктора так сказать)

Какой кайф создавать синглтоны
кайф, чтобы у тебя был объектик.=) Так и на сердце спокойней, и диспатчить можно из него, и интерфейсы применять к нему, и удалять. И в целом приятнее.

Добавлено через 2 минуты
caseyryan
пожалуй! Хотя кем жеж это надо быть, чтобы изнутри этого класса пытаться создавать множество инстансов)) Эдак себя бить себя по рукам постоянно надо. )
А извне то все равно больше одного не создашь.

undefined
27.11.2017, 17:48
Это как раз приватизация конструктора так сказать)
в чем смысл наличия флага isConstructing?

ZergMaster
27.11.2017, 18:04
Это как раз приватизация конструктора так сказать)
в чем смысл наличия флага isConstructing?

ну как...Там же все понятно вроде. Это приватная boolean переменная, которая может быть изменена только изнутри класса. По умолчанию она равна false.
private static var _isConstructing:Boolean;
В конструкторе стоит её проверка, которая выдает ошибку при попытке создать экземпляр класса
public function MySingleton() {
if (!_isConstructing) throw new Error("Singleton, use MySingleton.instance");
}
что и ограничивает создание конструктора извне класса, так как данную переменную изменить можно только изнутри. Что мы и делаем в геттере instance, единожды разрешая создание конструктора.

public static function get instance():MySingleton {
if (_instance == null) {
_isConstructing = true;
_instance = new MySingleton();
_isConstructing = false;
}
return _instance;
}
причем данная операция выполняется только если переменная _instance не определена, если же экземпляр уже был создан, т1о мы просто возвращаем его, не трогая переменную _isConstructing, которая по прежнему, пребывая в false, ограничивает создание экземпляра данного класса извне.

Tails
27.11.2017, 18:17
Если в описаний класса указано что он - синглтон, а кто-то всё равно пытается использовать его как-то иначе, то этому человеку уже ничего не поможет..

Я просто хочу сказать, что "защита от дурака" должна быть уместна. Она должна быть только там, где действительно может быть допущена ошибка. Реальная, а не гипотетическая.

Appleman
27.11.2017, 18:19
у меня для этой задачи служит такой классик:



public function registerObject(object:Sprite, description:String):void
{
_bubbleObjects[object] = {target:object, description:description};

if (!_stage.hasEventListener(MouseEvent.MOUSE_OVER))
{
_stage.addEventListener(MouseEvent.MOUSE_OVER, mouseOverOutHandler, true);
_stage.addEventListener(MouseEvent.MOUSE_OUT, mouseOverOutHandler, true);
}
}



ZergMaster, а в чём преимущество такого метода - вешать слушатель на весь слой, перед навешиванием слушателей непосредственно на регистрируемые объекты?

undefined
27.11.2017, 18:20
В конструкторе стоит её проверка, которая выдает ошибку при попытке создать экземпляр класса
так, а зачем ее сначала в true выставлять,и тут же в false?
обычно делают так:

private var _instance:MySingleton;
private function MySingleton() {
...
}
public static function getInstance():MySingleton {
if (!_instance) _instance=new MySingleton();
return _instance;
}

и никакие флаги не нужны

ZergMaster
27.11.2017, 18:30
так, а зачем ее сначала в true выставлять,и тут же в false?
дык я ж написал. Чтобы нельзя было создать инстанс извне

undefined
27.11.2017, 18:40
ну так сам факт, что в _instance что-то сидит достаточно чтоб кидать ошибку.Зачем флаги еще какие-то?

ZergMaster
27.11.2017, 18:41
ZergMaster, а в чём преимущество такого метода - вешать слушатель на весь слой, перед навешиванием слушателей непосредственно на регистрируемые объекты?
в данном случае реализуется "баблинг" событий. Единожды добавляем на контейнер слушатели OVER и OUT, чтобы не создавать бесчисленное множество слушателей (ведь объектов с всплывающей подсказкой может быть очень много). Всякий раз, как событие срабатывает, проверяем, не идентичен ли target одному из объектов, которые мы зарегали в бабл-коллекции. Если да, то тут уже добавляем MOVE слушатель, чтобы двигать за мышкой подсказку. Когда же происходит OUT - слушатель удаляется. Таким образом, на все про все используется два постоянных и один временный слушатель (независимо от количества зарегестрированных на подсказку объектов).

Добавлено через 2 минуты
так, а зачем ее сначала в true выставлять,и тут же в false?
обычно делают так:

private var _instance:MySingleton;
private function MySingleton() {
...
}
public static function getInstance():MySingleton {
if (!_instance) _instance=new MySingleton();
return _instance;
}

и никакие флаги не нужны

все потому, что, как я уже писал, мы не можем в as3 сделать приватным конструктор. То есть
private function MySingleton() {
...
} не получится.
Соответственно, флаг обеспечивает невозможность сделать где-нибудь в коде
var mySingleton = new MySingleton()

undefined
27.11.2017, 18:51
а блин, это я с жавой попутал)
но все равно наличие флага избыточно.

Appleman
27.11.2017, 18:55
в данном случае реализуется "баблинг" событий. Единожды добавляем на контейнер слушатели OVER и OUT, чтобы не создавать бесчисленное множество слушателей (ведь объектов с всплывающей подсказкой может быть очень много). Всякий раз, как событие срабатывает, проверяем, не идентичен ли target одному из объектов, которые мы зарегали в бабл-коллекции. Если да, то тут уже добавляем MOVE слушатель, чтобы двигать за мышкой подсказку. Когда же происходит OUT - слушатель удаляется. Таким образом, на все про все используется два постоянных и один временный слушатель.


Ясно. А что с прекращением регистрации объектов? Насколько я понимаю, с одной стороны, при таком подходе никакие приёмники событий не "зависнут", и мы можем даже не делать "unregister" для добавленных объектов. У тебя даже такого метода в классе нет. С другой стороны, что будет происходить, если мы, например, захотим выводить численное значение какого-то параметра при наведении мышки на его шкалу? Мне кажется, твой класс не справится с такой задачей, т.к. description для графического объекта (собственно шкалы) он получил в момент её регистрации, и его уже будет не обновить.

ZergMaster
27.11.2017, 18:57
undefined
защита от дурака имеет место быть в Enterprise проектах.
Я согласен, что можно и так. Но, повторюсь, в этом случае предпочту ограничить доступ, если возможно. Для порядку.

Добавлено через 4 минуты
Appleman
Этот класс очень легко модифицировать под такие нужды. какой-нибудь метод в стиле
public function setDescription(object:Object, description:String):void
{
_bubbleObjects[object].description = description;
}

Appleman
27.11.2017, 19:36
Этот класс очень легко модифицировать под такие нужды. какой-нибудь метод в стиле

Согласен. Но всё-таки не будет ли проблемой отсутствие метода прекращения регистрации объектов и их изъятия из перечня отслеживаемых? Раздувание Dictionary....

caseyryan
27.11.2017, 20:26
пожалуй! Хотя кем жеж это надо быть, чтобы изнутри этого класса пытаться создавать множество инстансов)) Эдак себя бить себя по рукам постоянно надо. )
А извне то все равно больше одного не создашь.

Да почему изнутри то? Я же написал как можно создать сколько угодно таких экземлпяров. И сделал это извне. Сначала вызывается MySinglton.instance; Этот вызов ставит флаг isConstructring в true, а дальше создавай как обычные экземпляры его сколько угодно, он уже не синглтон
Туплю. Я не обратил внимание, что там флаг снова ставится в false сразу же после создания.
Да, способ хороший.
п.с. Малость офтоп, но мне нравится реализация синглтона в C# в Unity

using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance;

private static object _lockObject = new object();

public static T Instance {
get {
if (applicationIsQuitting) {
return null;
}

lock(_lockObject)
{
if (_instance == null) {
_instance = (T) FindObjectOfType(typeof(T));

if ( FindObjectsOfType(typeof(T)).Length > 1 ) {
return _instance;
}

if (_instance == null) {
GameObject singleton = new GameObject();
_instance = singleton.AddComponent<T>();
singleton.name = "(singleton) "+ typeof(T).ToString();
DontDestroyOnLoad(singleton);
}
}

return _instance;
}
}
}

private static bool applicationIsQuitting = false;

public void OnDestroy () {
applicationIsQuitting = true;
}
}

А дальше по этому шаблону создаем сколько угодно разных синглтонов всего лишь создав такой класс

using UnityEngine;

public class SomeSinglton: Singleton<SoundManager> {

}


И можно уже обращаться к нему

SomeSinglton.Instance;


Очень удобная штука, и мне ее катастрофически нехватает в as3 теперь)

ZergMaster
27.11.2017, 22:39
Согласен. Но всё-таки не будет ли проблемой отсутствие метода прекращения регистрации объектов и их изъятия из перечня отслеживаемых? Раздувание Dictionary....

не очень понял, в чем тут проблема?
delete _bubbleObjects[object]

Appleman
27.11.2017, 23:19
не очень понял, в чем тут проблема?
delete _bubbleObjects[object]

В исходном коде не было (или я не заметил :)). Я и спросил.

ZergMaster
28.11.2017, 04:47
В исходном коде не было (или я не заметил :)). Я и спросил.
конечно не было, потому что мне не было необходимости удалять. У меня этот класс используется просто как всплывающая подсказка. Навел на кнопку и тебе всплывает подсказка "Кнопка". И для моих целей нужно её один раз добавить и всё, удалять потом нет необходимости. Наоборот. Мне нужно, чтоб она всю программу лежала в Коллекции баблов.

Appleman
28.11.2017, 13:06
Всё ясно. На самом деле, красивое решение, я заценил. Правда не сразу допёр до нюанса с полным "разбором" всплывающих подсказок перед их изъятием со сцены по событию "отъезда" курсора. Сразу не сделал, поимел кучу наслоившихся дубликатов :)