- Performance V.S. Reusability
我想這是在OOP裡一個很大的tradeoff,我們必須在效能與框架設計之間取得平衡,考慮下面這一個簡單的效果:
我們手邊已經有一個CircleMotion的Class,如下:
/* CircleMotion.as */
package com.xinyu.motion{
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.display.DisplayObject;
import com.xinyu.science.Science;
public class CircleMotion2D extends Motion{
private var _timer:Timer;
private var _radian:Number = 0;
private var _radius:Number;
private var _speed:Number;
private var _dir:int;
private var _xOffset:Number;
private var _yOffset:Number;
private var _obj:DisplayObject;
public static const CW:int = 1;
public static const CCW:int = -1;
public function CircleMotion2D(obj:DisplayObject, xOffset:Number = 0 ,yOffset:Number = 0, radius:Number = 50, speed:Number = 0.1, fps:Number = 30, dir:Number = CircleMotion2D.CW, radOffset:int = 0){
_obj = obj;
_radius = radius;
_speed = Math.min(1, Math.max(speed, 0.001));
_dir = dir;
_xOffset = xOffset;
_yOffset = yOffset;
_radian = Science.degToRad(radOffset%360);
_timer = new Timer(1000/fps);
_timer.addEventListener(TimerEvent.TIMER, onTimer);
}
private function onTimer(event:TimerEvent):void{
_obj.x = _radius*Math.cos(_radian) + _xOffset - _radius;
_obj.y = _radius*Math.sin(_radian) + _yOffset;
_radian += _dir * _speed;
if(_radian > 2*Math.PI){
_radian = 0;
}
}
override public function start():void{
_timer.start();
}
override public function stop():void{
_timer.stop();
}
override public function get running():Boolean{
return _timer.running;
}
}
}
所以以直覺來講,RingMotion2D應該要直接沿用CircleMotion2D的定義,簡單的改變每個Object的initial radian就可以達到環繞的效果,所以RingMotion2D理論上是會這樣撰寫:
/* RingMotion2D.as*/
package com.xinyu.motion{
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.display.DisplayObject;
import com.xinyu.motion.CircleMotion2D;
import com.xinyu.science.Science;
public class RingMotion2D extends Motion{
private var _obj:Array;
private var circleM2D:Array;
public static const CW:int = 1;
public static const CCW:int = -1;
public function RingMotion2D(obj:Array, xOffset:Number = 0 ,yOffset:Number = 0, radius:Number = 100, speed:Number = 0.1, fps:Number = 30, dir:Number = RingMotion2D2.CW){
_obj = obj;
circleM2D = new Array(_obj.length);
for(var i:uint = 0; i < _obj.length; i++){
circleM2D[i] = new CircleMotion2D(_obj[i], xOffset, yOffset, radius, speed, fps, dir, 360 / _obj.length * i);
}
}
override public function start():void{
for(var i:uint = 0; i < _obj.length; i++){
circleM2D[i].start();
}
}
override public function stop():void{
for(var i:uint = 0; i < _obj.length; i++){
circleM2D[i].stop();
}
}
override public function get running():Boolean{
return circleM2D[0].running;
}
}
}
乍看之下,這樣的寫法很簡潔且沒有問題,但是實際去做測試之後問題就會浮現了,那就是效能,一旦物件增多,fps加快,這個Class所用到的timer也會是成比例成長,縱使是再快的CPU也會有些微的影響 (當然你可能會質疑說,誰沒事會在畫面上產生幾百顆球? 但別忘了這個效果可能只是你的project的功能之一,且並不是所有的瀏覽者的電腦都是那麼快的)。
為了降低程式的效能成本,我必須重新定義一個RingMotion,如下:
/* RingMotion2D.as */
package com.xinyu.motion{
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.display.DisplayObject;
import com.xinyu.science.Science;
public class RingMotion2D extends Motion{
private var _timer:Timer;
private var _radian:Array;
private var _radius:Number;
private var _speed:Number;
private var _dir:int;
private var _xOffset:Number;
private var _yOffset:Number;
private var _obj:Array;
public static const CW:int = 1;
public static const CCW:int = -1;
public function RingMotion2D(obj:Array, xOffset:Number = 0 ,yOffset:Number = 0, radius:Number = 100, speed:Number = 0.1, fps:Number = 30, dir:Number = RingMotion2D.CW){
_obj = obj;
_radian = new Array(_obj.length);
for(var i:uint = 0; i<_obj.length; i++){
_radian[i] = Science.degToRad(360/_obj.length * i);
}
_radius = radius;
_speed = Math.min(1, Math.max(speed, 0.001));
_dir = dir;
_xOffset = xOffset;
_yOffset = yOffset;
_timer = new Timer(1000/fps);
_timer.addEventListener(TimerEvent.TIMER, onTimer);
}
private function onTimer(event:TimerEvent):void{
for(var i:uint = 0; i < _obj.length; i++){
_obj[i].x = _radius * Math.cos(_radian[i]) + _xOffset - _radius;
_obj[i].y = _radius * Math.sin(_radian[i]) + _yOffset;
_radian[i] += _dir * _speed;
if(_radian[i] > 2*Math.PI){
_radian[i] = 0;
}
}
}
override public function start():void{
_timer.start();
}
override public function stop():void{
_timer.stop();
}
override public function get running():Boolean{
return _timer.running;
}
}
}
無可否認這樣的寫法的確違反了OOP的精神,但是為了效能不得如此,因為這樣寫不管有多少個DisplayObject,我們都只需要一個Timer。
看到這裡也許你會發現如果新的RingMotion2D裡只控制一個物件,那不就等於CircleMotion2D,所以理當CircleMotion2D應該要被RingMotion2D給取代掉才對? 未必,畢竟CircleMotion2D在處裡單顆物件上的效能會比RingMotion2D快一點點(也只有那麼微渺的一點點)。
No comments:
Post a Comment