PDA

Volledige versie bekijken : ArrayDecorator


Mediamonkey
%Europe/Berlin %952 %2006, 23:51
Ha mensen, tijd niets gepost!

Ik was 't beu om elke keer een methode te schrijven die een array doorloopt, op zoek naar een al dan niet bestaand item. 't Leek me erg handig om een Decorator class (= Design Pattern) te schrijven die een aantal bruikbare methodes in een array knalt wanneer ik daar behoefte aan heb.

Nu zijn er natuurlijk tig ouwe AS1 prototypes die een Array uitbreiden met leuke functionaliteiten, dus die kun je in deze class inbouwen. Dan hoef je daar nooit weer naar te kijken.

Ik bedenk me ineens dat 't wel leuk zou zijn om met een argument het aantal, of een paar specifieke methodes te kunnen toekennen aan een array ipv alles in eens. Daar ga ik nog even mee spelen. :)

* edit - zo, dat ging makkelijker dan ik dacht. De code is aangepast om nu met initialize een subset van methodes mee te geven die je graag wil toevoegen aan de array ipv de complete zooi.

Zoals altijd, heb je opmerkingen of verbeteringen.. laat maar horen!
- Mediamonkey -

/**
* ArrayDecorator class
*
* Description:
* This class "decorates" an array with nifty extra's. Methods such as contains(), a must have, really!
* Feel free to add more functionalities in this class, and send me a copy ;)
*
* Author: Bart Wttewaall, Mediamonkey
* Date: 26 september 2006
*
* Usage:
* var arr:Array = new Array("one", "two", "three");
* ArrayDecorator.initialize(arr);
* arr.addItemAt(1, "one and a half");
* trace(arr.contains("three"));
* trace(arr);
*
*/

class nl.mediamonkey.array.ArrayDecorator {

// save an instance of itself (Singleton)
private static var decorator:ArrayDecorator = undefined;

private static var mixinProps:Array = ["addItem", "addItemAt", "addItemsAt", "getItemAt",
"removeItemAt", "removeItemsAt", "removeAll", "replaceItemAt", "contains", "getIndicesOnItems",
"findItem", "findItemsFromArray", "sortItems", "sortItemsBy"];

// inherent properties of array
public var length:Number;
public var splice:Function;
public var slice:Function;
public var sortOn:Function;
public var reverse:Function;
public var sort:Function;

public static function initialize(obj:Object, subset:Array):Boolean {
if (decorator == undefined) decorator = new ArrayDecorator();

//obj = obj.prototype; // We don't want to decorate an entire class, just one instance

var mixin:Array = (subset != undefined) ? subset : mixinProps;
for (var i=0; i<mixin.length; i++) {
var prop:String = mixin[i];
obj[prop] = decorator[prop];
}

// now hide all these new methods from a trace()
//_global.ASSetPropFlags(obj, mixinProps, 1);

return true;
}

function addItem(value):Void {
addItemAt(length, value);
}

function addItemAt(index:Number, value):Void {
if (index<length) {
splice(index, 0, value);
} else if (index>length) {
return;
}
this[index] = value;
}

function addItemsAt(index:Number, newItems:Array):Void {
index = Math.min(length, index);
newItems.unshift(index, 0);
splice.apply(this, newItems);
newItems.splice(0, 2);
}

function getItemAt(index:Number) {
return this[index];
}

function removeItemAt(index:Number) {
var ret = this[index];
removeItemsAt(index, 1);
return ret;
}

function removeItemsAt(index:Number, len:Number):Void {
splice(index, len);
}

function removeAll(Void):Void {
splice(0);
}

function replaceItemAt(index:Number, itemObj):Void {
if (index<0 || index>=length) return;
this[index] = itemObj;
}

public function contains(item):Boolean {
var result:Boolean;
if (item instanceof(Array)) result = (findItemsFromArray(item).length > 0);
else result = (findItem(item) > -1);
return result;
}

public function getIndicesOnItems():Array {
var arr:Array;
if (arguments.length > 1) {
arr = arguments;
} else if (arguments.length == 1) {
var obj = arguments[0];
arr = (obj instanceof(Array)) ? obj : [obj];
} else return;

return findItemsFromArray(arr);
}

public function findItem(item):Number {
var i = length;
while (i--) if (this[i] === item) return i;
return -1;
}

public function findItemsFromArray(arr:Array):Array {
var result:Array = new Array(arr.length);
var iLength:Number = length;
var jLength:Number = arr.length;
var found:Number = 0;

for (var i=0; i<iLength; i++) {
for (var j=0; (j < jLength) && (found < arr.length); j++) {
if (this[i] == arr[j]) {
result[j] = i;
found++;
break;
}
}
}
return (result.length > 0) ? result : null;
}

function sortItems(compareFunc, optionFlags):Void {
sort(compareFunc, optionFlags);
}

function sortItemsBy(fieldName, order):Void {
// old "asc" or "desc"
if (typeof(order)=="string") {
sortOn(fieldName);
if (order.toUpperCase()=="DESC") {
reverse();
}
} else {
sortOn(fieldName, order);
}
}
}

Tha Narie
%Europe/Berlin %980 %2006, 00:31
Is het niet beter om de decorate-functionaliteit los te houden van de mixin-functies?
En op deze manier zou je ook makkelijk andere ArrayUtils kunnen toevoegen, zonder die class hoeven aan te passen.

Ik weet niet of dat in het Pattern past, en of het goed gaat werken, maar leek mij wel een leuk idee ;)

Ziet er in ieder geval verder leuk uit!

TheDutch
%Europe/Berlin %220 %2006, 06:17
Hey Mediamonkey!

Mooie functionaliteiten voor arrays. Ik vraag me alleen af of het het maken van een Decorator op de manier hoe jij dit nu hebt gedaan wel juist is. Je zou op deze manier net zo goed een ExtendedArray class kunnen maken die Array extend. Het is namelijk niet zo dat het om hele specifieke functies gaat dat de voordelen van een Decorator class echt duidelijk zichtbaar zijn. Je kunt al deze functies bijvoorbeeld op deze manier ook als Util neerzetten en alleen gebruiken wanneer je ze nodig hebt.

Het Decorator design patterns komt in deze opzet alleen goed uit wanneer je heel specifiek gaat werken. Maak aparte Decorators voor search, sort, crud, etc. Dan kan je per soort Decorator bekijken of je die op dat moment nodig hebt. Dan pas gebruik je het Decorator Design Pattern zoals het bedoeld is.

Graag hoor ik jouw gedachten :).

ps. De functies zijn wat inconsistent wat betreft access en returntype keywords :P.

Tha Narie
%Europe/Berlin %373 %2006, 09:57
Maak aparte Decorators voor search, sort, crud, etc. Dan kan je per soort Decorator bekijken of je die op dat moment nodig hebt. Dan pas gebruik je het Decorator Design Pattern zoals het bedoeld is.

Opzich doet hij dit al dmv de subset parameter :)

TheDutch
%Europe/Berlin %395 %2006, 10:29
Klopt! Echter is dat niet volgens het design pattern :).
Elke Decorator class moet zijn eigen doel hebben. Lamp voorbeeld:

class Lamp()
==========
Methods:

Lamp()
turnOn()
turnOff()


Decorator class SfeerLampDecorator
===========================
Methods:

dim()
kleur()


Decorator class DiscoLampDecorator
===========================
Methods:

flikker()
randomKleur()
draai()

Zoals je ziet heeft elke Decorator zijn eigen doel met betrekking tot het geen het decoreert. Niet alleen is dat zoals het hoort volgens het design pattern. Het maakt het voor toekomstig gebruik ook een heel stuk overzichtelijker cq. eenvoudiger.

Tha Narie
%Europe/Berlin %427 %2006, 11:15
Is helemaal duidelijk zo. :)

Alleen is het met Array's iets vager dan met Lampen of andere custom classes, aangezien die echt een apart doel hebben.Je gebruikt meestal de ene of de andere.
Bij een Array kan je in 1 regel 3 verschillende soorten functies nodig hebben.

Bij primitieve datatypes ligt dit verschil (vooral in het begin) nogal vaag.
Ga je echt extreme functionaliteiten schrijven (10 soorten search-algoritmes), dan wordt het wat duidelijker :)

Wat MediaMonkey heeft gedaan is meer een BasicArrayDecorator, met wat extra basis-functionaliteit, die je vaak nodig gaat hebben.
Hiernaast kunnen nog andere ArrayDecorators geschreven worden voor andere doelen :)

Ik denk dat dat sowieso het 'probleem' van Design Patterns is; het wordt pas goed duidelijk bij grotere 'projecten', als ze optimaal benut en gebruikt kunnen worden

TheDutch
%Europe/Berlin %449 %2006, 11:47
Misschien dat zijn Decorator inderdaad beter BasicArrayDecorator zou kunnen heten omdat dat zijn 'doel' meer verduidelijkt.

Je hebt ook helemaal gelijk dat Design Patterns vooral bij grote projecten meer tot hun recht komen. Je gaat ook geen MVC framework gebruiken voor een site van de bakker op de hoek.

Zoals jij (The Narie) al weet ben ik op mijn werk bezig met het maken van een MVC framework voor Coldfusion. Ik benader alles zo los als mogelijk omdat dit voor onze projecten gewoon nodig is. Vandaar dat ik de Decorator van MediaMonkey wel nu al zou opsplitsen zodat ik later zonder veel moeite kan uitbreiden. Maar om te sharen op FlashFocus is de opzet zoals hij nu is wellicht beter ;).

Hoe dan ook blijf ik het geweldig vinden om te discusseren over patterns :D.

Mediamonkey
%Europe/Berlin %499 %2006, 12:59
Haha leuk dat dit een flinke discussie op gang brengt. Dit subforum doet z'n naam eer aan.

Het doel van mijn class is inderdaad een 'lazy' manier om snel extra basisfunctionaliteiten in een array te proppen. 'Tuurlijk kun je een Array extenden, maar dan heb je te maken met een nieuw type waar je een Array naar toe moet casten, of erger. Ik wou op het moment van schrijven alleen een contains() check uitvoeren, maar werd moe van 't elke keer opnieuw schrijven en m'n class verontreinigen met util methods. Nu kun je bijvoorbeeld snel een contains-check uitvoeren op bv. arguments:

public function calculate() {
if (arguments.length > 0) {
ArrayDecorator.initialize(arguments, ["contains"]);
if (!arguments.contains(123)) return;
}
}

En ja, dit had ook prima in een util class gekunt, maar ik vond de benadering die o.a. in de DataProvider werd gebruikt interessant om eens mee te experimenteren. Dus voor mijn doel is een pattern zeker overkill, maar hey.. 'twas fun.

TheDutch
%Europe/Berlin %797 %2006, 20:08
'Tuurlijk kun je een Array extenden, maar dan heb je te maken met een nieuw type waar je een Array naar toe moet casten, of erger.

Dat is toch niet helemaal waar hoor. Wanneer jij een class hebt genaamd ExtendedArray die Array extend valideert hij gewoon als Array. Dit komt omdat hij Array extend. Wanneer je bijvoorbeeld naar ActionScript 3.0 gaat kijken dan is alles te valideren als Object omdat alles van Object inherit. Dus casting is bij het extenden niet nodig.


En ja, dit had ook prima in een util class gekunt, maar ik vond de benadering die o.a. in de DataProvider werd gebruikt interessant om eens mee te experimenteren. Dus voor mijn doel is een pattern zeker overkill, maar hey.. 'twas fun.
Dat is het zeker, interessant om mee te experimenteren en fun! :D

Tha Narie
%Europe/Berlin %867 %2006, 21:48
Dat is toch niet helemaal waar hoor. Wanneer jij een class hebt genaamd ExtendedArray die Array extend valideert hij gewoon als Array. Dit komt omdat hij Array extend.
En dat is ook niet helemaal waar ;)
Althans, niet goed doorgedacht :P

Aangezien heel veel functies in ActionScript een normale Array returnen, en de []-methode ook een normale Array aanmaakt, zul je (als je de extended-functionaliteiten wilt gebruiken) dat object toch eerst moeten casten.
Alleen bij het zelf-defineren werkt het wel zo. :)

Net als Strings die je dan als new String moet aanmaken ipv met "", en Booleans met new Boolean ipv gewoon true/false en numbers met new Number :P
(Tenminste, dat was mijn eerste ervaring met AS2, later niet meer getest ofzo.)

Mediamonkey
%Europe/Berlin %875 %2006, 22:00
Inderdaad. Want hoe wou je anders een ExtendedArray maken uit de Array arguments? Hij bestaat al als Array-type, dus zonder te casten, te decoraten of te kopiëren krijg je er geen ExtendedArray uit.

Andersom heb je wel gelijk. Een ExtendedArray zal met instanceof(Array) gewoon true teruggeven. In mijn voorbeeld kun je dus van bv. de Array arguments een "ExtendedArray" maken, en wanneer de extra methodes niet meer nodig zijn simpelweg terugcasten naar Array.

public function calculate() {
ArrayDecorator.initialize(arguments, ["contains"]);
trace("arguments contains 'hello': "+arguments.contains("hello"));
for (var i in arguments) trace(i+" : "+arguments[i]);

trace("-----------------");

arguments = Array(arguments);
for (var i in arguments) trace(i+" : "+arguments[i]);
}

TheDutch
%Europe/Berlin %897 %2006, 22:31
En dat is ook niet helemaal waar ;)
Althans, niet goed doorgedacht :P

Het is helemaal waar wat ik zeg ;).

Ik heb MediaMonkey verkeerd begrepen. Ik dacht dat hij te lui was om zijn datatypen te veranderen van Array naar ExtendedArray, want dat zou in de praktijk niet nodig zijn (al is het wel netter).


Andersom heb je wel gelijk. Een ExtendedArray zal met instanceof(Array) gewoon true teruggeven. In mijn voorbeeld kun je dus van bv. de Array arguments een "ExtendedArray" maken, en wanneer de extra methodes niet meer nodig zijn simpelweg terugcasten naar Array.

Mijn punt dus precies, haha.

Wat jullie verder zeggen klopt uiteraard! Daar ben ik het helemaal mee eens :).