PDA

Просмотр полной версии : Перечисляемый тип на AS3


Фенёк
28.01.2014, 19:48
Вечер добрый, господа соратники.

Давеча загадали мне загадку: необходимо написать на AS3 перечисляемый тип. Реализовать используя классы Enumeration, IntEnumeration, UintEnumeration, StringEnumeration, которые так же полагается реализовать самостоятельно. Ну и приведены примеры использования


public class UnitType extends UintEnumeration
{
public static const HERO:UnitType = new UnitType(1);
public static const WARRIOR:UnitType = new UnitType(2);
public static const MAGE:UnitType = new UnitType(3);

public static const ENEMY:UnitType = new UnitType(4);



public function UnitType(val:uint)
{
value = val;
}
}



public class UnitUpgradeType extends StringEnumeration
{
public static const U1:UnitUpgradeType = new UnitUpgradeType("U1");
public static const U2:UnitUpgradeType = new UnitUpgradeType("U2");
public static const U3:UnitUpgradeType = new UnitUpgradeType("U3");
public static const U3:UnitUpgradeType = new UnitUpgradeType("U4");



public function UnitUpgradeType(val:String)
{
value = val;
}
}


Модифицировать приведенные в использовании классы запрещается.

Я прочел некоторое количество статей по реализации этого паттерна, но в условия задачи он не уложился. А не уложился он потому, что при невозможности модифицировать класс, необходимо было реализовать метод etElementsList(enumerationClass:Class):Vector.<Enumeration> который должен был отдавать все значения перечисления.

Насчет задачи вывода значений перечисления меня посетила только одна мысль: ведь любой класс, это на самом деле объект класса Class и по идее должна быть возможность просмотреть его свойства, ведь задавая статические константы мы задаем константы для объекта-класса.

то есть например

for (var property:String in SomeClass)
{
trace(property)
}


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

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

Вобщем у меня два вопроса, в связи с моим провалом:

1) Есть ли возможность при таких условиях добраться-таки до всех значений статических констант?
2) Есть ли возможность при таких условиях закрыть классы для создания экземпляров извне?

expl
28.01.2014, 20:34
1) Есть ли возможность при таких условиях добраться-таки до всех значений статических констант?

Узнать тип объекта: getQualifiedClassName(this) (http://www.flasher.ru/forum/showthread.php?t=132772;
Получить список полей можно при помощи методов: getDefinitionByName, describeType

Только зачем вам этот список полей? Ведь в приведённом вами коде нет примеров где он нужен,
а чтобы эти примеры компилировались достаточно просто написать:


public class UintEnumeration
{
public uint value;
}

public class StringEnumeration
{
public string value;
}


Т.е. чего-то не договариваете вы - примеров использования UnitType нет.
Как тогда понять какой функционал надо запихнуть в UintEnumeration?

2) Есть ли возможность при таких условиях закрыть классы для создания экземпляров извне?
Отличить создание и присвоение статическому полю от всех остальных созданий? Только каким-нибудь злым колдунством.

Akopalipsis
28.01.2014, 21:00
Вот только если Вы хотите узнать о статических свойствах класса, то describeType не поможет, он их не видит.

silin
28.01.2014, 21:27
describeType нормально видит статические константы
насчет закрыть классы для создания экземпляров извне при таких условиях - действительно непонятно о каких условиях речь, "Модифицировать приведенные в использовании классы запрещается" или что?

СлаваRa
28.01.2014, 21:29
@Akopalipsis, вы не правы, describeType работает и показывает статику если в качестве аргумента методу будет передан класс, если же необходимо работать с объектом, то можно сделать так:
describeType(this["constructor"])

Добавлено через 7 минут
А еще можно вот тут почитать http://www.flasher.ru/forum/blog.php?b=131

Akopalipsis
28.01.2014, 22:11
Да, ошибся, я вот как пробовал -
package
{
public class StorageClass
{
public static const STATIC_PROP:TestClass = new TestClass();
}
}
package
{
import flash.display.Sprite;
import flash.utils.describeType;

public class Main extends Sprite
{
public function Main()
{
var storageClass:StorageClass = new StorageClass();
trace(describeType(StorageClass));//так видит, а вот если ссылку передать, то нет
}

}
}

dimarik
28.01.2014, 23:38
public class Enumeration {
public function Enumeration(id:*) {
this.id = id;
}

public var id:*;

public function etElementsList(/*enumerationClass:Class этот параметр, к.м.к., не имеет смысла */):Vector.<Enumeration> {
return null;
}

public function toString():String {
return this.id.toString();
}
}

public class UintEnumeration extends Enumeration {
public function UintEnumeration(id:uint) {
super(id);
}
}

public class UnitType extends UintEnumeration {
public static const HERO: UnitType = new UnitType(1);
public static const WARRIOR: UnitType = new UnitType(2);
public static const MAGE: UnitType = new UnitType(3);
public static const ENEMY: UnitType = new UnitType(4);

private static const _elementsList:Vector.<Enumeration> = new <Enumeration>[HERO, WARRIOR, MAGE, ENEMY];

public function UnitType(id:uint) {
super(id);
}

public override function etElementsList(/*enumerationClass:Class*/):Vector.<Enumeration> {
return _elementsList.slice();
}

}

Фенёк
29.01.2014, 01:17
Ого, оперативно вы )

Насчет describeType большое спасибо, это очень помогло. Я уж было думал, что все это можно сделать если только каким-нибудь безумно грязным хаком, а тут оказывается есть готовая утилитка.

Сегодня-завтра накидаю демку того, что хотелось бы получить в результате, чтобы исключить неоднозначности в вопросах, а то пост писал несколько второпях, уходя с работы. Больше так не буду )

djyamato
29.01.2014, 10:06
интересная тема, спасибо всем

Фенёк
29.01.2014, 11:50
Итак, поехали.

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


Написать на ActionScript перечисляемый тип и объяснить, почему это хорошо.
Реализуется классами Enumeration, IntEnumeration, UintEnumeration, StringEnumeration.

Использование.

public class UnitType extends UintEnumeration
{
public static const HERO:UnitType = new UnitType(1);
public static const WARRIOR:UnitType = new UnitType(2);
public static const MAGE:UnitType = new UnitType(3);

public static const ENEMY:UnitType = new UnitType(4);



public function UnitType(val:uint)
{
value = val;
}
}

public class UnitUpgradeType extends StringEnumeration
{
public static const U1:UnitUpgradeType = new UnitUpgradeType("U1");
public static const U2:UnitUpgradeType = new UnitUpgradeType("U2");
public static const U3:UnitUpgradeType = new UnitUpgradeType("U3");
public static const U3:UnitUpgradeType = new UnitUpgradeType("U4");



public function UnitUpgradeType(val:String)
{
value = val;
}
}

Ничего больше в классах UnitType и UnitUpgradeType быть не должно (иначе задание теряет смысл).
Все значения перечисляемого типа могут быть объявлены его статическими константами и только ими. Если это не так - проект падает сразу после компиляции или содержит варнинги.
Есть метод function getElementsList(enumerationClass:Class):Vector.<Enumeration> - отдает все значения перечисления.
Есть метод getElementByValue(value:*, enumClass:Class):Enumeration - отдает элемент перечисления по значению.

Результат - флешка, трейсами доказывающая работоспособность; код.


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

Для чего такая длинная цепочка наследования классов(Enumeration, UintEnumeration, StringEnumeration), я так и не понял, поскольку условиями задачи запрещается даже вызывать конструктор суперкласса в UnitType и UnitUpgradeType. Из-за чего иерархия классов в моем представлении сложлась следующим, весьма нелепым, образом:

класс Enumeration:


public class Enumeration
{
protected var _value:*;

public function Enumeration()
{

}

public function get value():*
{
return _value;
}

}

класс UintEnumeration:

public class UintEnumeration extends Enumeration
{

public function UintEnumeration()
{

}

}


Класс StringEnumeration

public class StringEnumeration extends Enumeration
{

public function StringEnumeration()
{

}

}


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

Кстати, попутно еще один вопрос: могу ли я в классах наследниках переписать так метод get value() чтобы он возвращал значения другого типа? Так как в данной ситуации, это могла быть единственная польза принесенная таким наследованием.

Далее. Я так понял что методы getElementsList(enumerationClass:Class):Vector.<Enumeration> и getElementByValue(value:*, enumClass:Class):Enumeration это методы, напрмер, объекта Main. В этом случае передаваемй параметр все таки имеет существенное значение.

expl
29.01.2014, 14:21
То есть по факту, классы наследники мне пришлось оставить пустыми, потому как больше ничего поделать я с ними не могу.

Может они хотят что-то типа такого (хотя не факт):


public class Enumeration
{
public function Enumeration()
{

}

private var _commonValue:*;
public function get commonValue():*// Это для обобщенных алгоритмов над всеми перечислениями
{
return _commonValue;
}

}

public class UintEnumeration extends Enumeration
{

public function UintEnumeration()
{

}

private var _value:uint;
public function set value(value:uint):uint
{
_value = value;
_commonValue = value;
}
public function get value():uint
{
return _value;
}
}

могу ли я в классах наследниках переписать так метод get value() чтобы он возвращал значения другого типа? Так как в данной ситуации, это могла быть единственная польза принесенная таким наследованием.
Пользу тут вообще искать сложно, они это, видимо, чисто ради примера сделали
На практике такой стиль не подводил(без всяких базовых классов - они практически бесполезны и с забиванием большого гвоздя на проверки создания не в том месте - они не от чего не защищают):

public class UnitType
{
// Список всех значений (не всегда нужен, кстати)
public static const allUnits:Vector.<Unit> = new Vector.<Unit>();

public static const Unit1:UnitType = new UnitType(1);
public static const Unit1:UnitType = new UnitType(2);
public static const Unit1:UnitType = new UnitType(3);

private var _code:uint;
public function get code():uint
{
return _code;
}

public function UnitType(code:uint)
{
_code = code;
allUnits.Add(this);// заполнение списка
}
}

А громоздить сюда рефлексию - это лишние проблемы при дебаге и никакой пользы

Akopalipsis
29.01.2014, 14:47
Вчера не спрашивал, потому что думал, пойму. Но нет, я и статью прочёл и код смотрел сто раз.
Объясните на простом языке, для чего это.

silin
29.01.2014, 15:13
если честно, не совсем понял в чем проблема
вот такой вариант, например
package
{
import flash.display.Sprite;

public class Main extends Sprite
{

public function Main():void
{

var list:Vector.<Enumeration> = Enumeration.getElementsList(UnitType);
trace("list : " + list);
var element:Enumeration = Enumeration.getElementByValue(1, UnitType);
trace("element : " + element);
var isHero:Boolean = (element === UnitType.HERO);
trace("isHero : " + isHero);
}

}

}

//=================================//
import flash.utils.describeType;

class Enumeration
{
public var value:*;

public function Enumeration(value:*)
{
this.value = value;
}

public static function getElementsList(enumClass:Class):Vector.<Enumeration>
{
var list:Vector.<Enumeration> = new Vector.<Enumeration>();
var constList:XMLList = describeType(enumClass).constant;

for each (var item:XML in constList)
{
list.push(enumClass[item.@name])
}

return list;

}

public static function getElementByValue(value:*, enumClass:Class):Enumeration
{
var constList:XMLList = describeType(enumClass).constant;

for each (var item:XML in constList)
{
if (enumClass[item.@name].value == value)
return enumClass[item.@name];
}

return null;
}
}

//=================================//
class UintEnumeration extends Enumeration
{

public function UintEnumeration(value:uint)
{
super(value);

}

}

//=================================//
class UnitType extends UintEnumeration
{

public static const HERO:UnitType = new UnitType(1);
public static const WARRIOR:UnitType = new UnitType(2);

public function UnitType(value:uint)
{
super(value);

}

}

Фенёк
29.01.2014, 15:56
expl, Я вот вобщем-то тоже придерживаюсь идеи о том, что адекватнее всего иметь список констант уже заранее сформированный. при том может быть, если уж имеем дело со списком то и хранить их например в хэше и запрашивать по ключу, нежели изголяться через describeType.

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

Если вопрос в том зачем такая реализация, то тут уже я сильно затрудняюсь вам как-то это прокомментировать. Люди составлявшие задание по всей видимости пытались сделать сделать из него загадку, мол «вот вам странные условия задачи, попробуйте используя их дать нам ответ, какой мы хотим увидеть. Попытайтесь использовать нашу логику». Признаться честно глядя на все это мне кажется что такой способ можно охарактеризовать только одним словом - ИЗВРАТ.

silin, Если честно, мне кажется, что проблема на деле в странно поставленной задаче. Сам запутался и вас запутал. В общем на деле пожалуй ваш вариант наиболее приближен к тому, что как я понял от меня хотели. Сформирую свой вариант и, если будет какой-то ответ по заданию, расскажу, что же там была за черная магия в описании.

Вобщем всем спасибо, вы мне очень помогли )

Akopalipsis
15.02.2014, 15:51
Хочется полюбопытствовать, в конструкторах классов присутствует параметр id, так вот он для того, чтобы в каком-нибудь особом случаи сравнивать по нему? Типа -
...
public static const HERO:UnitType = new UnitType(1);
...
public static const HERO:uint = 0x000000;
...
if(HERO.id == HERO){...}
Правильно я понял?

silin
15.02.2014, 16:42
>>Правильно я понял?
все правильно, в особых случаях параметр id можно использовать для сравнения в операторе if

Akopalipsis
16.02.2014, 07:25
Немного не по теме - а при помощи describeType можно только public свойства увидеть?
Почему-то мне кажется, что можно и к остальным обращаться-считать, но у меня не получается и мне уже кажется, что я что-то напутал.

silin
16.02.2014, 12:03
да, при помощи describeType можно только public свойства увидеть

Akopalipsis
16.02.2014, 13:58
silin Спасибо! Ужас.

taluks
20.09.2016, 01:54
если кому еще интересно то выглядеть должно так так (https://github.com/taluks/EnumerationTypeAS3)

caseyryan
20.09.2016, 06:43
если кому еще интересно то выглядеть должно так так (https://github.com/taluks/EnumerationTypeAS3)

Это все равно не ENUM. Тут, по сути, просто замена примитивных типов ссылочными, и не более.

taluks
20.09.2016, 15:18
Это все равно не ENUM. Тут, по сути, просто замена примитивных типов ссылочными, и не более.

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

dimarik
21.09.2016, 20:12
var classConstructor:Class = this["constructor"];
...
/*
Вместо
var className:String = getQualifiedClassName(this);
if (className == getQualifiedClassName(Enumeration))
throw new ArgumentError("Enumeration class cannot be instantiated.");
*/

// Можно прост:
if (classConstructor == Enumeration) {
throw new ArgumentError("Enumeration class cannot be instantiated.");
}


Так, накатило прост.

caseyryan
21.09.2016, 20:33
var classConstructor:Class = this["constructor"];
...
/*
Вместо
var className:String = getQualifiedClassName(this);
if (className == getQualifiedClassName(Enumeration))
throw new ArgumentError("Enumeration class cannot be instantiated.");
*/

// Можно прост:
if (classConstructor == Enumeration) {
throw new ArgumentError("Enumeration class cannot be instantiated.");
}


Так, накатило прост.

А если еще проще

if (this is Enumeration) {
throw new ArgumentError("Enumeration class cannot be instantiated.");
}


?

dimarik
21.09.2016, 20:35
не, так не катит. исправляйся )

И еще я стринги во флеше не люблю. Лучше так

var ctor:Class = (this as Object).constructor;

taluks
22.09.2016, 22:46
да, я согласен полностью. но хочу заметить это не как не влияет и используется только при отладке

RAlfDog
27.08.2017, 00:37
А что будет, если где-нибудь я сделаю так:

UnitType.HERO.value = 4;

или

UnitType.HERO.value = 5;


Добавлено через 27 минут
и вот здесь вот:

if(vectorEnumeration[i].value == value)

мне кажется лучше делать так:

if(vectorEnumeration[i].value === value)


Добавлено через 49 часов 31 минуту
В целом идея отличная, очень нужная :) Спасибо автору за тему!
Нужно только немного улучшить:
- не давать напрямую создавать экземпляры UnitType (все возможные значения должны быть объявлены только статически)
- сделать IntEnumeration, UintEnumeration, StringEnumeration абстрактными
- в Enumeration проверку на абстракцию вынести перед проверкой наличия в словаре списка экземпляров класса