Flyweightパターンは、オブジェクトの大量生成を行うようなアプリケーションでパフォーマンスの向上が期待できる手法である。 具体的には、大量のオブジェクトの中で共有できるオブジェクトは共有させ、オブジェクト生成によるメモリ消費を軽減する。
例えば文章を作成するアプリケーションを考えよう。 この文章の中でアルファベット文字をそれぞれオブジェクトとして扱う方が繊細なレイアウトが出来て都合がよかったとしよう。 この場合の問題は、文字が記入されるたびにその文字オブジェクトのためのメモリが確保されてしまうことにある。 そこで例えばAの文字をキャラクターの形状情報だけを保持した共有オブジェクトとして作成し、 Aの文字が記入されるたびに共有オブジェクトを使って描画させるという手法をとる。 こうすることで、アルファベットの数だけ共有オブジェクトを作成すれば それ以上のメモリを消費することなくいくらでもアルファベットが記述できることになる。 文字ごとにフォントサイズやフォントカラーをかえたいのであれば それらを外部情報(描画コンテクスト)として別クラスに記録しておき、 それを共有オブジェクトの描画メソッドに引数として渡すという手法で対応することができる。
Flyweightとは文字通り軽量という意味である(ボクシングのフライ級)。 使い回し可能な部分には最低限必要な情報のみ保持させ軽量化し、その他バリエーションは外部設定にまかせるというスタンスがFlyweightの特徴である。
以下の例では、六角形タイルを画面上にランダムに敷き詰めている。 六角形を描画するクラス(PolygonDrawer.as)を共有オブジェクトとしている。 六角形のサイズ、色、描画位置などのバリエーション情報は、 描画コンテクストとして外部クラス(Context.as)に記述しておき、 共有オブジェクトの描画メソッド(PolygonDrawer.draw)に引数として渡している (例では六角形の描画位置しか指定していない)。
//main.as package { import flash.display.Sprite; import flash.geom.Point; import flyweight.Context; import flyweight.PolygonDrawer; [SWF(width="550",height="400")] public class main extends Sprite{ private static const STAGE_WIDTH:Number = 550; private static const STAGE_HEIGHT:Number = 400; private static const RADIUS:Number = 10; private static const SIDES:uint = 6; private static const GRID_X:Number = 3*RADIUS; private static const GRID_Y:Number = Math.sqrt(3)*RADIUS; private static const FLYWEIGHT_TAG:String = "HexagonDrawer"; public function main(){ var context:Context = new Context(this,new Point(),RADIUS,SIDES,0x0,0xFFFFFF); var drawer:PolygonDrawer = new PolygonDrawer(FLYWEIGHT_TAG); for (var i:int=0; i<1000; i++){ context.center = this.shapeCenter(); drawer.draw(context); } } /** * @private * @return Center of a shape that is randomly chosen. */ private function shapeCenter():Point{ var randomX:Number = STAGE_WIDTH*Math.random(); var randomY:Number = STAGE_HEIGHT*Math.random(); var remainderX:Number = randomX % GRID_X; var x:Number = int(randomX / GRID_X) * GRID_X; var y:Number = int(randomY / GRID_Y) * GRID_Y; if (remainderX % (GRID_X/2) < 1){ x += GRID_X/2; y += GRID_Y/2; } return new Point(x,y); } } }
//Flyweight.as (Abstract) package flyweight{ public class Flyweight{ protected var _tag:String; public function get tag():String{ return _tag; } /** * Constructor * * @param tag ID of a flyweight object. */ public function Flyweight(tag:String){ _tag = tag; } } }
//PolygonDrawer.as (Flyweight) package flyweight{ import flash.display.Graphics; public class PolygonDrawer extends Flyweight{ /** * Constructor * * @param tag ID of a flyweight object. */ public function PolygonDrawer(tag:String){ super(tag); } /** * @param Drawing context. */ public function draw(context:Context):void{ var g:Graphics = context.target.graphics; var originX:Number = context.center.x; var originY:Number = context.center.y; var rad:Number = context.radius; var sides:uint = context.sides; var strokeColor:uint = context.strokeColor; var fillColor:uint = context.fillColor; g.lineStyle(0,strokeColor); g.beginFill(fillColor); for (var i:int=0; i<=sides; i++){ var theta:Number = 2*Math.PI*i/sides; var x:Number = originX + rad*Math.cos(theta); var y:Number = originY + rad*Math.sin(theta); if (i == 0){ g.moveTo(x,y); } else { g.lineTo(x,y); } } } } }
//Context.as package flyweight{ import flash.display.Sprite; import flash.geom.Point; public class Context{ protected var _target:Sprite; public function get target():Sprite{ return _target; } protected var _center:Point; public function set center(value:Point):void{ _center = value; } public function get center():Point{ return _center; } protected var _radius:Number; public function get radius():Number{ return _radius; } protected var _sides:uint; public function get sides():uint{ return _sides; } protected var _strokeColor:uint; public function get strokeColor():uint{ return _strokeColor; } protected var _fillColor:uint; public function get fillColor():uint{ return _fillColor; } /** * Constructor * * @param target The target object to be drawn on (i.e., canvas). * @param center x-y location in the target object. * @param radius Size of a draw object. * @param sides Number of the sides of the draw object (e.g., 3 for a triangle). * @param strokeColor Stroke color of the draw object. * @param fillColor Filling color of the draw object. */ public function Context(target:Sprite, center:Point, radius:Number, sides:uint=3, strokeColor:uint=0x0, fillColor:uint=0x0){ _target = target; _center = center; _radius = radius; _sides = sides; if (_sides < 3) _sides = 3; _strokeColor = strokeColor; _fillColor = fillColor; } } }
import flyweight.FlyweightFactory; //Register a flyweight object. var factory:FlyweightFactory = FlyweightFactory.sharedFactory(); var fw:Flyweight = new Flyweight("flyweightObjID"); factory.addFlyweight(fw);
import flyweight.FlyweightFactory; //Withdraw the flyweight object. var factory:FlyweightFactory = FlyweightFactory.sharedFactory(); var obj:Flyweight = factory.flyweightWithTag("flyweightObjID");
//FlyweightFactory.as package flyweight{ public class FlyweightFactory{ protected static var _sharedFactory:FlyweightFactory; protected var _flyweights:Array; public function set flyweights(value:Array):void{ _flyweights = value; } public function get flyweights():Array{ return _flyweights; } /** * Constructor * * @param pvt PrivateClass. */ public function FlyweightFactory(pvt:PrivateClass){ } public static function sharedFactory():FlyweightFactory{ if (FlyweightFactory._sharedFactory == null){ FlyweightFactory._sharedFactory = new FlyweightFactory(new PrivateClass()); FlyweightFactory._sharedFactory._flyweights = new Array(); } return FlyweightFactory._sharedFactory; } public function addFlyweight(fw:Flyweight):void{ if (this.flyweightWithTag(fw.tag) == null){ _flyweights.push(fw); } } public function flyweightWithTag(tag:String):Flyweight{ for each (var fw:Flyweight in _flyweights){ if (fw.tag == tag){ return fw; } } return null; } } } //Dummy class for the singleton. class PrivateClass{ public function PrivateClass(){ } }