The Button Factory: Labels and Buttons from the Same Factory Methods
The other day I was looking at an ActionScript 3.0 program, and the developer used a ‘UI Factory.’ I looked at it, and while the program did indeed get UIs from the UIFactory class, it wasn’t a design pattern. Instead the UI Factory was just a single class with a bunch of methods for building different parts of a UI. Included in the factory, though, was a call to another class that also was part of the overall UI.
Basically, the call from one factory product to another made the requesting product a client. So, I thought, why not create a factory method design pattern where one part of a product called another to complete the product? The design pattern would be a straightforward Factory Method pattern, and the call from within the pattern would follow the same call through the Creator as a call from a general Client class would be. In this way, I would be able to use the same interfaces (abstract classes) for calls within the pattern as well as from outside the pattern by an external Client class.
A Button and Label Factory
The basic process is the same used in the post describing Lazy Initialization on this blog. The difference is that one product uses another product in the factory process. To get started, take a look at Figure 1 that shows how an external client and the UIButton both make requests through the UICreator. The external client can get either a button or just the label, but when a button is requested, it then requests a label to go with it.
The button uses a Sprite object for the base of a graphic gradient shape using a round rectangle. The label is put into a Sprite as well and then added to the button. The Sprite is inherited from the same UIProduct class.
Creators and Products
Just like any other Factory Method, the pattern uses abstract classes for both the UICreator and the UIProduct. The following listing shows that both abstract classes have methods that use the same set of parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | //Creator package { import flash.display.Sprite; import flash.errors.IllegalOperationError; // ABSTRACT Class (should be subclassed and not instantiated) public class UICreator { protected var product:UIProduct; // ABSTRACT Method (must be overridden in a subclass) public function factoryMethod(msg:String,hor:Number,v:Number,col:uint,w:Number,h:Number):Sprite { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); return null; } } } //Product package { import flash.display.Sprite; import flash.errors.IllegalOperationError; // ABSTRACT Class (should be subclassed and not instantiated) class UIProduct { protected var ui:Sprite; public function getUI(msg:String,hor:Number,v:Number,col:uint,w:Number,h:Number):Sprite { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); return null; } } } |
The “abstracting” of the classes provides a good deal of flexibility and bindings are loose. The concrete implementations are only two of a larger planned UI set. However, it’s not difficult to add any concrete UI you want.
The Concrete Label Maker and Product
First, take a look at the concrete label and label creator. The creator uses lazy initialization, and the concrete label product uses TextField and TextFormat objects for the label product.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | //Concrete Label Creator package { import flash.display.Sprite; public class SpriteLabelMaker extends UICreator { public override function factoryMethod(msg:String,hor:Number,v:Number,col:uint,w:Number,h:Number):Sprite { if (! product) { product=new SpriteLabel(); } return product.getUI(msg,hor,v,col,w,h); } } } //Concrete Label Product package { import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFieldAutoSize; import flash.display.Sprite; public class SpriteLabel extends UIProduct { private var txtLabel:TextField; private var txtFormat:TextFormat; public override function getUI(msg:String,align:Number,v:Number,col:uint,w:Number,h:Number):Sprite { ui=new Sprite(); txtFormat=new TextFormat(); txtFormat.font="Arial Black"; txtFormat.color=0xffffff; txtLabel = new TextField(); txtLabel.defaultTextFormat = txtFormat; txtLabel.text = msg; txtLabel.autoSize = TextFieldAutoSize.LEFT; txtLabel.selectable = false; txtLabel.x = align / 2; txtLabel.y = h/4; ui.addChild(txtLabel); return ui; } } } |
By including both TextField and TextFormat objects, the look and feel of the button labels have a good deal of flexibility. Currently set to create only white buttons, the color parameter (col) could be used to add more appropriate colors if needed.
The Concrete Button Maker and Product
Like the label, the concrete implementations of the creator and product abstract classes use the same set of method parameters. However, the button product acts as a client calling for a label product through the UICreator interface (abstract class). Both return a Sprite object, but when the button object appears, it always has a label with it. So a request for a button object is also a request for a label object. Labels, however, may be requested by a client without having to get a button object as well.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | //Button concrete Creator package { import flash.display.Sprite; public class SpriteButtonMaker extends UICreator { public override function factoryMethod(msg:String,hor:Number,v:Number,col:uint,w:Number,h:Number):Sprite { if (! product) { product=new SpriteButton(); } return product.getUI(msg,hor,v,col,w,h); } } } //Button concrete Product package { import flash.display.Sprite; import flash.display.GradientType; import flash.display.SpreadMethod; public class SpriteButton extends UIProduct { private var lbl:UICreator; public override function getUI(msg:String,hor:Number,v:Number,col:uint,w:Number,h:Number):Sprite { ui= new Sprite(); var fill:String = GradientType.LINEAR; var spread:String = SpreadMethod.PAD; ui.graphics.beginGradientFill(fill, [col, 0x70c656],[1,1],[127,255],null,spread,"rgb",0); //Button shape (x,y,width, height, corner) ui.graphics.drawRoundRect(0,0,w, h, 8, 8); ui.graphics.endFill(); ui.x=hor, ui.y=v; ui.buttonMode = true; ui.useHandCursor = true; lbl=new SpriteLabelMaker(); ui.addChild(lbl.factoryMethod(msg,hor,v,col,w,h)); return ui; } } } |
Both the graphic portion and label portion of the button use the ui property inherited and implemented from the UIProperty class. In that respect the button product is recursively formed. It is built by embedding the label Sprite into the button Sprite.
Finally, here’s an example of a Client. It creates two different buttons based on the arguments.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package { import flash.display.Sprite; public class UIClient extends Sprite { private var btnMk:UICreator; private var lbl:UICreator; private var btn:Sprite=new Sprite; private var btn2:Sprite=new Sprite; public function UIClient() { btnMk=new SpriteButtonMaker(); //(for label,x,y,color,width,height) btn=btnMk.factoryMethod("Click Here",40,40,0x53933f,120,50); addChild(btn); btn2=btnMk.factoryMethod("Magic",40,110,0x0000cc,100,32); addChild(btn2); } } } |
One important feature I tested with this little design pattern was whether the buttons could indeed be used as buttons. Most importantly, I wanted to make sure that they could handle event listeners that in turn could handle events. Fortunately, they worked like any other button, and all that was necessary was to make sure that the buttonMode and useHandCursor properties were set to true. I have a feeling that this is a little application that I’ll be re-using a lot and tweaking a lot more!




Bill Sanders
Recent Comments