PDA

View Full Version : [example] Design Patterns:Decorators


Flash Gordon
05-25-2009, 01:11 AM
This isn't a question but just an example of a pattern I've been finding really helpful lately and it's the awesome decorator pattern. It's a pattern I've neglected a lot until as of late. It's seems to have become my best friend now. I use it to hide asynchronous behavior and to add other "common" behavior.

So why am I writing this? Well, I think using the decorator pattern on Duck, Geese, and Coffee rather sucks, don't you? (Sorry Head First, I love you guys but thought you could use some help). I've never made a Duck in Flash and never will. However, I make sliders, programmatic skins (which I have decorators for resizing behavior), data controllers, centering scripts all of which use decorators.

I've written an example (attached) of a very common use that I have for decorators and that I think you all should have as well. It comes in the form of a slider control. Sliders can be used for a bunch of things: your scrollbar slider to the right there >>>, a volume slider, a panning slider, etc. The problem I was having was for a volume slider, the typical set up is the bottom of the slider is the min value and the top is the max value. Well, this worked great for me for volume controls but for a slider that controlled scrolling, it was completely backwards. I could either invert the values everywhere I needed it or add a property of ALL of my sliders to toggle an inversion for me.....or....I could add write 1 decorator that would invert all the values for me. This puts my logic all in 1 location rather than several and leaves my original class closed from modification. So here's what I did and it's pretty simple:

I first made an AbstractSliderDecorator so that I could make many decorators for my sliders (and I did) which would make it easier to make the decorators. All i did was delegate all of my property calls to a slider passed into the constructor.

package com.fg.controls
{
import flash.events.EventDispatcher;

import com.fg.events.SliderEvent;

public class AbstractSliderDecorator extends EventDispatcher implements ISlider
{
private var slider:ISlider;

public function get minValue():Number { return slider.minValue; }
public function set minValue(value:Number):void { slider.minValue = value; }

public function get maxValue():Number { return slider.maxValue; }
public function set maxValue(value:Number):void { slider.maxValue = value; }

public function get value():Number { return slider.value; }
public function set value(value:Number):void { slider.value = value; }

public function AbstractSliderDecorator(slider:ISlider):void
{
this.slider = slider;
this.slider.addEventListener(SliderEvent.SLIDER_CH ANGE, bubbleHandler, false, 0, true);
}

private function bubbleHandler(e:SliderEvent):void
{
dispatchEvent( e );
}
}
}


So then I made my InvertedSliderDecorator which was pretty easy now. All I didn't was overrode the value properties to give the appearance the value is inverted. Which I thought was pretty slick.

package com.fg.controls
{
import flash.events.EventDispatcher;

import com.fg.events.SliderEvent;

public class InvertedSliderDecorator extends AbstractSliderDecorator
{
private var slider:ISlider;

override public function get value():Number { return invertValue(slider.value); }
override public function set value(value:Number):void { slider.value = invertValue(value); }

public function InvertedSliderDecorator(slider:ISlider):void
{
super(slider);
this.slider = slider;
addEventListener(SliderEvent.SLIDER_CHANGE, sliderChangeHandler, false, 1, true);
}

private function invertValue(value:Number):Number
{
var difference:Number = value - slider.minValue;
return slider.maxValue - difference;
}

private function sliderChangeHandler(e:SliderEvent):void
{
e.value = invertValue(e.value);
}
}
}


And lastly here is my slider class. This class is a sample of my slider. It SHOULD be abstracted out and I hinted at where that should be by exposing protected methods. The only difference between a HorizontalSlilder and VeritcalSlider could be the commits to the value property.

package com.fg.controls
{
import flash.display.DisplayObject;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.MouseEvent;
import flash.geom.Rectangle;

import com.fg.events.SliderEvent;

public class VerticalSlider extends EventDispatcher implements ISlider
{
private var dummy:Shape;

private function get limitsChange():Number { return maxValue - minValue; }

private var _handle:Sprite;
public function get handle():Sprite { return _handle; }

private var _track:DisplayObject;
public function get track():DisplayObject { return _track; }

private var _bounds:Rectangle;
public function get bounds():Rectangle { return _bounds; }
public function set bounds(value:Rectangle):void
{
if (_bounds && _bounds.equals(value)) return;
_bounds = value;
}

private var _minValue:Number = 0;
public function get minValue():Number { return _minValue; }
public function set minValue(value:Number):void
{
_minValue = value;
}

private var _maxValue:Number = 1;
public function get maxValue():Number { return _maxValue; }
public function set maxValue(value:Number):void
{
_maxValue = value;
}

private var _value:Number;
public function get value():Number { return _value; }
public function set value(value:Number):void
{
if (_value == value) return;
_value = value;
commitValueProperty();
dispatchEvent( new SliderEvent(SliderEvent.SLIDER_CHANGE, this.value) );
}

public function VerticalSlider()
{
super();
dummy = new Shape();
}

public function initAssets(handle:Sprite, track:DisplayObject):void
{
_handle = handle;
_track = track;

handle.buttonMode = true;
handle.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDownHandler, false, 0, true);
handle.addEventListener(MouseEvent.MOUSE_UP, handleMouseUpHandler, false, 0, true);

bounds = track.getBounds(handle.parent);
bounds.height -= handle.height;
bounds.width = 0;
bounds.x = handle.x;
}

protected function commitValueProperty():void
{
var percent:Number = 1-(value - minValue) / limitsChange
var location:Number = percent * bounds.height + bounds.y;
handle.y = location;
}

protected function calculateValue():void
{
var percent:Number = 1-(handle.y - bounds.y) / bounds.height;
var calculatedValue = percent * limitsChange + minValue;
this.value = calculatedValue;
}

protected function handlePressed():void
{
handle.startDrag(false, bounds);
dummy.addEventListener(Event.ENTER_FRAME, dummyEnterFrameHandler, false, 0, true);
}

protected function handleReleased():void
{
handle.stopDrag();
dummy.removeEventListener(Event.ENTER_FRAME, dummyEnterFrameHandler);
}

private function dummyEnterFrameHandler(e:Event):void
{
calculateValue();
}

private function handleMouseDownHandler(e:MouseEvent):void
{
handlePressed();
handle.stage.addEventListener(MouseEvent.MOUSE_UP, handleMouseUpHandler, false, 0, true);
}

private function handleMouseUpHandler(e:MouseEvent):void
{
handleReleased();
handle.stage.removeEventListener(MouseEvent.MOUSE_ UP, handleMouseUpHandler);
}

}

}


package com.fg.controls
{
import flash.events.IEventDispatcher;

public interface ISlider extends IEventDispatcher
{
function get minValue():Number;
function set minValue(value:Number):void;

function get maxValue():Number;
function set maxValue(value:Number):void;

function get value():Number;
function set value(value:Number):void;

}
}


If you look at the attached fla/swf you'll see two sliders: one has the decorator on it and the other does not. I could now use my decorator on an HorizontalSlider as well as my VerticalSlider (or even an crazy LoopDeLoopSlider). Same code reused and easily maintainable. And there are many other decorators that could be applied to a slider such as a EvenValueSliderDecorator for instance.

THANKS DECORATORS!

wvxvw
05-25-2009, 02:03 AM
Mmmm... I think there was a discussion on this very same issue before... well, now I just happen to have a better example of what I was trying to say then.
What you are using is Inheritence pattern, which doesn't make it worse or better then Decorator, but it's just different... The point of decorator is the ability to create it at runtime, not to have a precompiled one...

If you don't mind looking through quite a bit of not polished code, here's an example, of what may be classified as decorator:
this is a class that extends Sound and it is created at runtime from the externally loaded resource and randomply generated descriptor:
http://code.google.com/p/e4xu/source/browse/#svn/trunk/src/org/wvxvws/encoding

First see what SWFCompiler class does in compileMP3SWF() method, then see the MP3Transcoder class and SWFTag + extending classes in the tag folder.
The use of the class may be found here:

http://code.google.com/p/e4xu/source/browse/trunk/src/air-sources/SoundCompiler.mxml

This is a dummy AIR application that I used for testing this class. I have also a production variant of this, but it is closely bound to my other project and it's not in the SVN, so I cannot show the entire flow, but, hope you can guess what it does in the end. (Essentially it than calls Loader#loadBytes(bytesOfCompiledSWF) and grabs the new class definition from Loader#content, which allows me later to instantiate a new generated class).

Sorry to be a bore... ;)

Flash Gordon
05-25-2009, 02:58 AM
Sorry bro, as I've pointed out to you before (in a more subtle fasion) you're description of the Decorator pattern is simply incorrect and even Michael (post 5 & 6) (http://www.actionscript.org/forums/showthread.php3?t=204870) (the man at patterns) agrees and so does the GoF and Head First.

Trying reading http://www.as3dp.com/?s=decorator ActionScript 3.0 Easy and Practical Decorator Design Pattern post (or wikipedia (http://en.wikipedia.org/wiki/Decorator_pattern)) to get a clearer explanation of the decorator pattern. The intention of my post is not to describe what the decorator pattern is to anyone but to show a practical application of how to use it the Flash world which Head First fails to do.

I understand you'll never agree with me on this and I don't expect you to. However, you can help by starting your own thread about the great deception of the decorator pattern that GoF, Head First, as3 design pattern, wikipedia, and me have all bought into. Sorry to be so blatant, but it gets tiring tip-toeing around issues. Even with that said, i wishing you the best in the flash world, we're still friends, and I know you're a good programmer.

wvxvw
05-25-2009, 10:21 AM
The decorator pattern can be used to make it possible to extend (decorate) the functionality of a class at runtime.
This is the quote from the wiki link you just gave...
And thanks for the compliments, so far I'm posting a lot, I know that a t average every 100th post of mine is a total nonsense... well, maybe this one happen to be 101st :)

rrh
05-25-2009, 08:08 PM
That quote might be a poor choice of words.

This is how I understand it:

//slider2 is undecorated
slider2 = new InvertedSliderDecorator(slider2);
//slider2 is decorated

With ordinary inheritance, it would determine the functionality of slider2 when it was instantiated. With the application of a decoration, the functionality of slider2 can be extended at any time.

So I don't think the distinction is runtime vs. compile time, but at instantiation vs. after instantiation.

The other value of a decorator is multiple decorators can be applied to the same instance in any combination.

Flash Gordon
05-25-2009, 08:28 PM
::nods::

I've been wrong many times before, but as far as I'm aware classes can not be created at run time. Classes are created at author/compile time, objects are created at run time. I do see any benefit of creating classes at run time. Even the xml which would describe a class is created at author (edit: changed from other) time and then at compile time and would still be nothing more than a convenience language like mxml.

Decorator are a way of dynamically adding behavior to an object and having that object "look" the same way but behave (slightly) different.

It's a difference in terminology that's the confusing part. I would consider this runtime or dynamically modifiying the behavior of slider

var slider:ISlider = new VerticalSlider();
if (foobar) slider = new InvertedSliderDecorator(slider);
if (fudcake) slider = new EvenValueSliderDecorator(slider);

but i think rrh said it pretty well. Something to remember when trying to identify a pattern is not how the objects interacts (the uml pattern) but what the intention is.

But let me finish the quote started above

The decorator pattern can be used to make it possible to extend (decorate) the functionality of a class at runtime. This works by adding a new decorator class that wraps the original class......

Subclassing adds behavior at compile time whereas decorating can provide new behaviour at runtime.


(p.s...sorry or being so darn forward wvxvw...roomates suck, it effects life, yada yada I'm sure you understand..urgh)

MichaelxxOA
05-25-2009, 11:43 PM
I doubt I'm going to add any clarification to this ... but I do have, what I believe, is a decent example of decorator.

I think (I and think being the operative words here) you are both right. I think that wvxvw's quote says it best.

The decorator pattern can be used to make it possible to extend (decorate) the functionality of a class at runtime.

Anything that satisfies that would seem, to me, to be a decorator.

Having said that, here is one way to use this idea.

Create one interface for how you want to iterate over collections of objects.

http://aidlia.com/resources/decorator/Iterator.as.txt

Define two collections of objects and how they can be iterated (I chose the Array and DisplayList).

http://aidlia.com/resources/decorator/ArrayIterator.as.txt
http://aidlia.com/resources/decorator/DisplayListIterator.as.txt

Now when you are iterating you are working at a higher level of abstraction. This means that things you do when iterating can now be thought of on this level. For example, how can you iterate through only specific types of objects?

http://aidlia.com/resources/decorator/TypedIterator.as.txt

^ and that is your decorator.

Here are two instances where this decorator is being used (in real production code):

http://aidlia.com/resources/decorator/Sequence.as.txt
http://aidlia.com/resources/decorator/ScreenElement.as.txt

Hope that helps, great stuff FB and wvxvw.

wvxvw
05-25-2009, 11:49 PM
FG:
I live alone in the center of the city, palms and other evergreen plants, tropical fruits etc... I can see the sea from the the balcony of my room, and it takes me less then 5 minutes to walk to the beach :) and, in general, I enjoy life... mmm... besides few minor disadvantages of my location it's pretty cool here :)

But! And I really don't intend to harm your feelings and hope you will accept my critics in good spirit, or, at least, suppose I just blubber because I like to :)

If you would look into my example you'd see that in my case the class is being truly generated at runtime, and it does what the decorator is meant for, while, in your example you're achivening the same goal, but, you have your class precompiled, which is an essential difference between what Inheritance, Subclassing, Interface on one side and Decorator on the other side are.
You just take it for granted that AS3 classes cannot be generated at runtime. Here I would partially agree, because it is not a trivial task, but it is not completely impossible - the proof is in my first post.

If you'll read until this line... and you don't mind a lot more reading - see this example of what is realy a whatmacallit "classical" example of Decorator:
http://209.85.129.132/search?q=cache:4U_dvq_ZJrAJ:httpdyn.cs.huji.ac.il/moodles/cs08/file.php/67125/Lectures/OOP_Streams_2.ppt+ByteArrayInputStream+decorator&cd=2&hl=en&ct=clnk&gl=il
(if the link doesn't show, thy this: httpdyn.cs.huji.ac.il/moodles/cs08/file.php/67125/Lectures/OOP_Streams_2.ppt )

I.e. if it was possible to do in AS3, it would look something like this:

var someSlider:Slider = new {VerticalSlider & Slider}();
Well, I know this isn't a legit code... but it's the best I could invent to illustrate the idea...

MichaelxxOA
05-26-2009, 12:07 AM
Hm, IMO you are getting caught up in details. I agree that your implementation is a decorator. I just don't agree that it's the only way to implement decorator.

FB's presentation of decorator is even actually pretty much inline with the GOF definition.

Flash Gordon
05-26-2009, 12:14 AM
no worries.

Call it what you want, but be aware you are going against common convention of what the community and the founders of design patterns have termed it. 3 people in this thread alone, as3dp (adobe), GoF, and Head First all call this a Decorator. Have read any of these? I've given 3 links and 5 sources that all agree with this. The only example you can give me are ones you've created. You are choosing to be iconoclastic. In the spirit of academic truthfulness and dissemination of information you should present your ideas as such: understand them and disagree by example and other supporting documentation.

Again, nothing personal, I may just be jealous of your nice view. ;)

wvxvw
05-26-2009, 12:31 AM
Well, I'm going to stick with captain Sisco anyway than... (as against founders!)
:P

Mazoonist
05-26-2009, 01:20 AM
Flash Gordon,

Thank you for sharing this. I have downloaded it and will study it. I like to study design patterns in my spare time. I have both of the design pattern books written for AS3. I appreciate that there are people like you that will share what they've discovered. It helps, like you said, to have real life examples that go beyond ducks and coffees (yeah, I've read that book, too).

More!

Flash Gordon
05-26-2009, 03:06 AM
@wvxvw No worries brother. My point is I'm just showing a "text book" example like Michael stated.

@Mazoonist, you're welcome and I'm planning on doing a series. I hope that I can lean on people like wvxvw to help with other patterns I'm less familar with in practical experience like "dumb" (as opposed to smart) Commands.

Mazoonist
05-28-2009, 02:16 AM
FG,

I like how you made an initAssets method, to which you simply send the thumb and track symbols that were drawn in the flash IDE. This seems like a very good way to do it, and I just wondered that I didn't think of it, too. Whenever I've made classes in Flash I usually would give the class new instances of those symbols as properties directly--then you have to make a note inside the class file that there must be symbol a and symbol v in the fla file's library, set for export, which seems to defeat the whole purpose of a self-contained class.

In fact, even in your case, you have to just know that the class has an initAssets method and that it's expecting instance a and instance b to be used for track and thumb. But that's better, at least all you have to know is the class's API. And you are free to use the class apart from any specific fla or fla's, which I like.

I had a couple of questions, if you don't mind. Is there some sort of advantage to making the VerticalSlider class extend EventDispatcher, rather than just extending Sprite (since Sprite already is an event dispatcher)?

Also, when InvertedSliderDecorator extends AbstractSliderDecorator, why doesn't it just inherit the slider property? Hmm.... I just tried this, and made the slider var "protected" in InvertedSliderDecorator (instead of "private") and then commenting out the line that sets the private var slider in AbstractSliderDecorator. And it seems to work just fine.

Flash Gordon
05-28-2009, 03:45 AM
Hey zoonist,

The approach of passing assets is one that I prefer of the other ways. Flash is very visual and there is ALWAYS objects on the stage/timeline. It's just easiest (for me...and makes most sense to me) to aggregate my assets into the object/class.

VerticalSlider isn't a display object. The assets on the stage are but VerticalSlider is just a controller of the aggregated assets. So there is no need for it to inherent from a DisplayObject (Sprite). If it shouldn't it doesn't....hehe.

InvertedSliderDecorator could inherit the slider property from AbstractSliderDecorator and maybe it should. However, I've gotten into the habbit (with the help of a friend) of closing off my classes pretty tightly to the outside world. As slider is private I can guarentee AbstractSliderDecortator will work and ain't nobody can screw with my needed class properties. However, if it was protected I can't be guarenteed the same thing. If my subclasses want a slider property they can simply make their own which is what I did. I tend to perfer it this way. When there are a bunch of protected properties and methods floating around, it sometimes hard to know how to implement the subclass. If I just see a protected method, well is the subclasses supposed to call it, should I override it, it is crucial, etc. So I try to only expose what is needed and make it as clear as possible for my subclasses to work with it.