TypeScript設計模式之組合、享元
看看用TypeScript怎樣實現常見的設計模式,順便復習一下。
學模式最重要的不是記UML,而是知道什么模式可以解決什么樣的問題,在做項目時碰到問題可以想到用哪個模式可以解決,UML忘了可以查,思想記住就好。
這里盡量用原創的,實際中能碰到的例子來說明模式的特點和用處。
組合模式 Composite
特點:以樹的形式展示對象的組合,并且可以以類似的方式處理每個枝點。
用處:當對象組合以樹狀存在,有父有子,并且對象的行為差不多時可以考慮組合模式,如菜單,游戲里的技能樹。
注意:遍歷組合的性能要求。
下面用TypeScript簡單實現一下組合模式:
技能樹麻煩了點,技能激活要引入觀察者模式,就以菜單為例吧。
菜單可以包括子菜單,點擊菜單項時有子菜單則顯示子菜單,沒有時觸發點擊事件。
先聲明一個抽象,包含菜單的名字,點擊事件和添加Child,一般情況下Menu會維護一個childs集合,通過這個集合來添加子菜單,不過這里沒有用這種方式,采用的是繼承一個集合來讓本身擁有集合的能力,這樣更方便,是父還是子可以通過字段來控制,也可以通過是否有child來表示。
abstract class MenuBase extends Array<MenuBase>{
name: string;
abstract click();
addChild(...childs: Array<MenuBase>){
childs.forEach(o=>this.push(o));
}
}
實現具體MenuItem類,一般情況下可以用兩個類,一個代表菜單,一個代表菜單項,不過這里就不需要區別是枝還是葉了,簡單一點,只用一個MenuItem代表所有。
可以傳click處理事件進來,click時會觸發click事件,另外如果有子則顯示所有子。
class MenuItem extends MenuBase{
constructor(public name: string, private clickFunc: () => string = undefined){
super();
}
click(){
console.log(`click ${this.name}`);
if(this.clickFunc){
console.log(this.clickFunc());
}
if(this.length > 0){
let childs = this.reduce((p, c)=><MenuBase>{name:`${p.name},${c.name}`}).name;
console.log(`${this.name}'s childs: ${childs}`);
}
}
}
現在來運行一下:
let root = new MenuItem('root');
let A1 = new MenuItem('A1');
let A2 = new MenuItem('A2', ()=>{return 'my name is A2'});
let B1 = new MenuItem('B1');
let B2 = new MenuItem('B2', ()=>{return 'my name is B2'});
let B3 = new MenuItem('B3', ()=>{return 'my name is B3'});
root.push(A1, A2);
A1.push(B1, B2, B3);
root.click();
A1.click();
A2.click();
B1.click();
結果:
click root
root's childs: A1,A2
click A1
A1's childs: B1,B2,B3
click A2
my name is A2
click B1
符合預期行為,這種組合就是非常簡單的,但如果組合得非常深且枝非常多時就需要考慮查找枝時的效率問題了,通常的辦法是采用緩存來把一些常用的查找結果緩存起來,避免頻繁遍歷。
享元模式 FlyWeight
特點:通過緩存來實現大量細粒度的對象的復用,從而提升性能。
用處:當有很多類似的對象要頻繁創建、銷毀時可以考慮享元模式,如線程池。
注意:對象的內部狀態和外部狀態。
下面用TypeScript簡單實現一下享元模式:
假設一個畫圖場景,里面有很多圖元,如圓,矩形等,由于量非常多,且經常刷新,每次刷新new出一批圖元,浪費內存是小事,臨時小對象太多導致頻繁GC才是麻煩事,UI會表現出卡頓。
現在用享元模式來解決這個問題,先定義一個圖形接口,可以畫圖,可以被刪掉,同時有個isHidden狀態來控制該圖元是否顯示,刪除也就是把isHidden設為true,并不真的刪除。
name是個標記,方便看記錄信息。
interface Element{
isHidden: boolean;
name: string;
draw();
remove();
}
實現兩個圖元:圓和矩形,draw只會畫不是hidden的,remove則是把isHidden設為true
class Circle implements Element{
isHidden: boolean = false;
constructor(public name: string){
}
draw(){
if(!this.isHidden){
console.log(`draw circle: ${this.name}`);
}
}
remove(){
this.isHidden = true;
console.log(`remove circle: ${this.name}`)
}
}
class Rectangle implements Element{
isHidden: boolean = false;
constructor(public name: string){
}
draw(){
if(!this.isHidden){
console.log(`draw rectangle: ${this.name}`);
}
}
remove(){
this.isHidden = true;
console.log(`remove rectangle: ${this.name}`)
}
}
下面建個圖元工廠來創建、維護圖元,工廠里維護了一個圖元池,包括圓和矩形,工廠除了創建圖元外,還可以刪掉所有圖元,得到圖元的數量(可見或不可見的)
class GraphFactory{
static readonly instance: GraphFactory = new GraphFactory();
private graphPool: {[key: string]: Array<Element>} = {}; // 圖元池
getCircle(newName: string): Circle{
return <Circle>this.getElement(newName, Rectangle);
}
getRectangle(newName: string): Rectangle{
return <Rectangle>this.getElement(newName, Rectangle);
}
getCount(isHidden: boolean){ // 圖元數據
let count = 0;
for(let item in this.graphPool){
count += this.graphPool[item].filter(o=>o.isHidden == isHidden).length;
}
return count;
}
removeAll(){ //刪除所有
console.log('remove all');
for(let item in this.graphPool){
this.graphPool[item].forEach(o=>o.isHidden = true);
}
}
//獲取圖元,如果池子里有hidden的圖元,就拿出來復用,沒有就new一個并Push到池子里
private getElement(newName: string, eleConstructor: typeof Circle | typeof Rectangle): Element{
if(this.graphPool[eleConstructor.name]){
let element = this.graphPool[eleConstructor.name].find(o=>o.isHidden);
if(element){
element.isHidden = false;
element.name = newName;
return element;
}
}
let element = new eleConstructor(newName);
this.graphPool[eleConstructor.name] = this.graphPool[eleConstructor.name] || [];
this.graphPool[eleConstructor.name].push(element);
return element;
}
}
代碼寫完了,分別拿5個圓和5個矩形測試:
- 先從工廠里分別創建出來并調用draw
- 打印出當前可見圖元數量
- 移除所有
- 分別打印可見和不可見圖元數量
- 從工廠里再次分別創建出5個并調用draw
- 再次分別打印可見和不可見圖元數量
const num = 5;
console.log('create elements');
for(let i = 1;i<=num;i++){
let circle = GraphFactory.instance.getCircle(`circle ${i}`);
let rectangle = GraphFactory.instance.getRectangle(`rectangle ${i}`);
circle.draw();
rectangle.draw();
}
console.log(`element count: ${GraphFactory.instance.getCount(false)}`);
GraphFactory.instance.removeAll();
console.log(`visible element count: ${GraphFactory.instance.getCount(false)}`);
console.log(`hidden element count: ${GraphFactory.instance.getCount(true)}`);
console.log('create elements again');
for(let i = 1;i<=num;i++){
let circle = GraphFactory.instance.getCircle(`new circle ${i}`);
let rectangle = GraphFactory.instance.getRectangle(`new rectangle ${i}`);
circle.draw();
rectangle.draw();
}
console.log(`visible element count: ${GraphFactory.instance.getCount(false)}`);
console.log(`hidden element count: ${GraphFactory.instance.getCount(true)}`);
結果:
create elements
draw rectangle: circle 1
draw rectangle: rectangle 1
draw rectangle: circle 2
draw rectangle: rectangle 2
draw rectangle: circle 3
draw rectangle: rectangle 3
draw rectangle: circle 4
draw rectangle: rectangle 4
draw rectangle: circle 5
draw rectangle: rectangle 5
element count: 10
remove all
visible element count: 0
hidden element count: 10
create elements again
draw rectangle: new circle 1
draw rectangle: new rectangle 1
draw rectangle: new circle 2
draw rectangle: new rectangle 2
draw rectangle: new circle 3
draw rectangle: new rectangle 3
draw rectangle: new circle 4
draw rectangle: new rectangle 4
draw rectangle: new circle 5
draw rectangle: new rectangle 5
visible element count: 10
hidden element count: 0
可以看到remove all后圖元全部hidden,再次創建時并不new出新的,而是從池子里拿出hidden的來復用,也就是利用享元模式,小對象的數量就可以限制在單次顯示最多的那次的數量,少于這個數量的都會從池子里拿,小對象也沒有頻繁創建銷毀,對內存,對GC都是有利的。
來自:http://www.cnblogs.com/brookshi/p/6545204.html