The ActionScript 3.0 Flyweight Saga: Part III Aggregation Aggravation, Stuff on the Stage and the Intrinsic State

Aggregation Aggravation

In the first installment of this Flyweight Saga, I noted that the relationship between a Flyweight and Flyweight Factory class is one of aggregation. The initial example shows that the Retrieve method in the Factory class returns an instance of IFlyweight, meeting the requirement of the proper connection between the two classes. In looking at one example in Java, (even with modest Java skills), the program clearly did not have such a relationship between the Factory and Flyweight. In fact, it claimed, that an object’s extrinsic state can be shared by classes. Now, maybe that was unfortunate wording because the big feature of the Flyweight design pattern is that the Flyweight can be a shared object, but only the intrinsic state can be shared. (Maybe the author meant that extrinsic states can be shared between classes in a Flyweight design pattern, and that’s probably right but is not a key feature of the pattern.)

Anyway, after looking at several different descriptions of aggregation, including the one provided by the GoF, it’s clear that the concept is one with fuzzy borders and can slip into either general composition or acquaintance. It implies that the Flyweight Factory aggregates the Flyweight—no Flyweight, no Factory. As a result, the life of the aggregator (Flyweight Factory) depends on the life of the aggregatee (Flyweight). Like acquaintance, aggregation is implemented with references or pointers rather than defining variables of once class in another. (Apparently C++ is an exception and does set up aggregation by defining variables from the aggregatee class.)

By and large the issue has not been especially significant so far because all of the output was using trace statements, and so output was largely confined to built-in features that only work when the code is run in test mode. It’s great for debugging up to a point, but developing with trace statements that do not take into account how certain graphic elements, especially those that are accessed by extending the Sprite classes, can generate unusable structures for applications that employ graphics and other elements that require the import and extension of other classes. In this next Flyweight example, we leave the realm of trace and use the graphics property (from the Sprite class) to draw solid balls using fill methods. The ball class extends Sprite and implements the interface. That’s all fine and good, but the aggregation becomes problematic in even the simplest example. In taking the general structure from the examples examined up to this point (Parts I and II of the Flyweight Saga), we can begin to see the trouble.

Because of the need to introduce the Sprite reference somewhere in the Flyweight by extending the Sprite class the Flyweight had to be referenced indirectly through the Concrete Flyweight (FlyBall). Does this count? I don’t know, but using an interface instead of an abstract class, I could see no way in getting in the needed Sprite extension. However, I do think that the Flyweight Factory did accomplish the manufacture of a single instance with multiple copies of an object with both the red and yellow balls. The following code is the first go at this using something other than trace. However, is it indeed a true Flyweight design pattern?

package 
{
	//Flyweight
 
	public interface IFlyweightBall
	{
		function doCircle (xPos:uint,yPos:uint,radius:uint):void;
	}
}
 
package 
{
	//Flyweight Factory
 
	public class BallFactory
	{
		protected var ballIntrinsic:Object={};
 
		public function BallFactory ()
		{
			ballIntrinsic["red"]=new FlyBall(0x990000);
			ballIntrinsic["yellow"]=new FlyBall(0xffff00);
 
		}
		public function getIntrinsic (key:String,... rest):FlyBall
		{
			switch (ballIntrinsic[key] != undefined)
			{
				case true :
					break;
				case false :
					ballIntrinsic[key]=rest[0];
					break;
			}
			return ballIntrinsic[key];
		}
	}
}
 
package 
{
	//Concrete Flyweight
 
	import flash.display.Sprite;
 
	public class FlyBall extends Sprite implements IFlyweightBall
	{
		protected var fill: uint;
 
		public function FlyBall (fill:uint)
		{
			this.fill=fill;
		}
 
		public function doCircle (xPos:uint,yPos:uint,radius:uint):void
		{
			graphics.beginFill (fill);
			graphics.drawCircle (xPos,yPos,radius);
			graphics.endFill ();
		}
	}
}
 
package 
{
	//Client class
	import flash.display.Sprite;
 
	public class BallClient extends Sprite
	{
 
		//Client data to provide extrinsic state details
		private var xPos;
		private var yPos;
		private var radius;
 
		public function BallClient ()
		{
			var ballFactory:BallFactory=new BallFactory;
 
			var cir1:FlyBall=ballFactory.getIntrinsic("red");
			shuffle ();
			cir1.doCircle (xPos,yPos,radius);
			addChild (cir1);
 
			var cir2:FlyBall=ballFactory.getIntrinsic("red");
			shuffle ();
			cir2.doCircle (xPos,yPos,radius);
			addChild (cir2);
 
			var cir3:FlyBall=ballFactory.getIntrinsic("yellow");
			shuffle ();
			cir3.doCircle (xPos,yPos,radius);
			addChild (cir3);
 
			var cir4:FlyBall=ballFactory.getIntrinsic("yellow");
			shuffle ();
			cir4.doCircle (xPos,yPos,radius);
			addChild (cir4);
 
			//Constructed in client
			var cir5:FlyBall=ballFactory.getIntrinsic("green",new FlyBall(0x009900));
			shuffle ();
			cir5.doCircle (xPos,yPos,radius);
			addChild (cir5);
		}
 
		private function shuffle ()
		{
			//Generate extrinsic states
			xPos=Math.round(Math.random() * 500);
			yPos=Math.round(Math.random() * 300);
			radius=Math.round(Math.random() * 100);
		}
	}
}

When this application is tested, you will see two red balls, two yellow balls and a green ball seen in Figure 1. The following line in the Flyweight Factory,

public function getIntrinsic (key:String,... rest):FlyBall

sets up the returned data as a concrete flyweight (FlyBall). In looking at the UML diagram for the Flyweight (See Part I of the Flyweight saga), the Flyweight Factory is an aggregation of the Flyweight. The aggregation relationship is one where the aggregator (Flyweight Factory) holds a reference to the aggregatee (Flyweight), but as you can see, the reference is to the Concrete Flyweight.

Figure 1

Abstract Class Changes Everything

The first ActionScript 3.0 Flyweight I worked was the one Jim Kremens had made for test of concept. It employed an abstract class rather than an interface, and I changed it to an interface. Now, I’ve come full circle back to the abstract class because it can be set up to inherit the needed elements of the Sprite class. This allows the Factory to establish a reference to the Flyweight rather than having to use a concrete Flyweight. The resulting application now has all of the right relationships.

package 
{
	//Abstract class Flyweight
	import flash.display.Sprite;
 
	public class FlyweightBall extends Sprite
	{	
			function doCircle (xPos:uint,yPos:uint,radius:uint):void
			{}
	}
}
 
package 
{
	//Flyweight Factory
 
	public class BallFactory
	{
		protected var ballIntrinsic:Object={};
 
		public function BallFactory ()
		{
			ballIntrinsic["red"]=new FlyBall(0x990000);
			ballIntrinsic["yellow"]=new FlyBall(0xffff00);
 
		}
		public function getIntrinsic (key:String,... rest):FlyweightBall
		{
			switch (ballIntrinsic[key] != undefined)
			{
				case true :
					break;
				case false :
					ballIntrinsic[key]=rest[0];
					break;
			}
			return ballIntrinsic[key];
		}
	}
}
 
package 
{
	//Concrete Flyweight
 
	public class FlyBall extends FlyweightBall
	{
		private var fill: uint;
 
		public function FlyBall (fill:uint)
		{
			this.fill=fill;
		}
 
		override function doCircle (xPos:uint,yPos:uint,radius:uint):void
		{
			graphics.beginFill (fill);
			graphics.drawCircle (xPos,yPos,radius);
			graphics.endFill ();
		}
	}
}
 
package 
{
	//Client class
	import flash.display.Sprite;
 
	public class BallClient extends Sprite
	{
 
		//Client data to provide extrinsic state details
		private var xPos;
		private var yPos;
		private var radius;
 
		public function BallClient ()
		{
			var ballFactory:BallFactory=new BallFactory;
 
			var cir1:FlyweightBall=ballFactory.getIntrinsic("red");
			shuffle ();
			cir1.doCircle (xPos,yPos,radius);
			addChild (cir1);
 
			var cir2:FlyweightBall=ballFactory.getIntrinsic("red");
			shuffle ();
			cir2.doCircle (xPos,yPos,radius);
			addChild (cir2);
 
			var cir3:FlyweightBall=ballFactory.getIntrinsic("yellow");
			shuffle ();
			cir3.doCircle (xPos,yPos,radius);
			addChild (cir3);
 
			var cir4:FlyweightBall=ballFactory.getIntrinsic("yellow");
			shuffle ();
			cir4.doCircle (xPos,yPos,radius);
			addChild (cir4);
 
			//Constructed in client
			var cir5:FlyweightBall=ballFactory.getIntrinsic("green",new FlyBall(0x009900));
			shuffle ();
			cir5.doCircle (xPos,yPos,radius);
			addChild (cir5);
		}
 
		private function shuffle ()
		{
			//Generate extrinsic states
			xPos=Math.round(Math.random() * 500);
			yPos=Math.round(Math.random() * 300);
			radius=Math.round(Math.random() * 100);
		}
	}
}

The Intrinsic State

Part II of the Flyweight Saga examined extrinsic states, which are part of the key operation in the Flyweight. However, it is the intrinsic state in the concrete flyweight where we need to look now. In looking at the UML diagram, the intrinsic state is stored in the Concrete Flyweight. So, while extending the Flyweight and implementing the key operation, we need to add an intrinsic state. In this case, the intrinsic case is placed as a parameter within the concrete flyweight constructor. The following lines are the crux of the intrinsic state:

protected var fill: uint;
public function FlyBall (fill:uint)
{
this.fill=fill;
}

The protected variable fill is the key here. As part of the constructor, the fill value is stored in the concrete flyweight. Then, when the operation calls the extrinsic parameters, the fill variable provides the color code for the concrete flyweight’s implementation of the instance. So, once the ball instance is created in the factory participant (BallFactory), the intrinsic value remains constant no matter what the values of the extrinsic values passed in the concrete flyweight method. As a result, it is possible to make multiple instances of the red and yellow balls using the intrinsic color that was in the associative array (ballIntrinsic). Thus, the line,

ballIntrinsic["red"]=new FlyBall(0×990000);

creates a “red” instance providing the fill color value in the construction parameter (0x990000). If the same key is called from the client, the getIntrinsic function checks to see if it exists or not. If it does, it simply returns the instance with the included color value stored as an intrinsic state. The extrinsic states, which include the radius, and x and y positions, simply re-use the existing red ball and change only the extrinsic states. (See Flyweight Saga Part II.)

3 Responses to “The ActionScript 3.0 Flyweight Saga: Part III Aggregation Aggravation, Stuff on the Stage and the Intrinsic State”


  1. 1 Andres A.

    As I was going over the code I realized that ‘cir1′ and ‘cir2′ hold references to the exact same object, which is obviously not an accident since that is the whole point of the Flyweight pattern, to reuse objects as much as possible. But then I asked myself how would you modify the extrinsic state of each button without affecting the other. For example, if you set the x and y position of cir2, you are essentially setting the x and y position of cir1 as well since both refer to the same Sprite object. Also, if you remove cir1 from the display, you’ll remove cir2.

    cir1.x = 200; //cir2.x will also increase by 200

    removeChild(cir2) //will also remove cir1’s circle graphic since both
    //cir1’s and cir2’s graphics are located in the same
    //Sprite object. So how do you remove, say cir2,
    //without removing cir1’s graphic?

  2. 2 Thomas F

    I can’t see the point, too.

  3. 3 Bill Sanders

    Hello Thomas and Andres,

    The whole issue of intrinsic and extrinsic states is at the heart of the Flyweight. I’ve re-done everything and put it into a 20pp paper that I’m taking to OOPSLA in Montreal, and I’ll probably put it here once I get their feedback. However, let me see if I can address your queries,

    One of the reasons that the Flyweight is so efficient is that it separates the intrinsic from the extrinsic and further from what you’d call the external. That means that a single object (a graphic in this case) can be re-used (a shared object) as a single instance. The extrinsic states amount to the variation that you allow beyond the intrinsic state. In this case, I used the x and y position along with some parameters for the shape of the object. Once beyound the extrinsic states, some interesting things begin to happen. For example, rotation is what might be called an “external” state. If I rotate any single reference to the object as far as rotation is concerned, the entire block will rotate. The same is true with any other state that was not built in as an extrinsic state. So if rotation is important for individual items (airplanes) I would simply add rotation as an extrinsic state.

    Also, this blog was written more to get developers (like yourself) involved in a discussion of the Flyweight. If you look around at examples, several get it wrong and others really do not use it as it is intended–bacically to take a single intrinsic state and vary it with extrinsic states in side a single instance to save space and increase the execution of a program. The original example in GoF is far better than the one I used because there are so many more references — a word processor re-using the glyph set. The intrinsic value is the character (a,b,c,d, etc.) and the extrinsic is the row and column on the page. In a thousand page book (The Java Bible)the letter “e” may be used 100,000 times. However, with a flyweight, it is rendered once and re-used as a shared object 100,000 times. Multiply that with all of the characters used over 1,000 pages and you can see why the Flyweight is so valuable.

Leave a Reply