用closure模擬friend宣告的效果
Tue, 29 Apr 2008 12:00:47 +0800之前在用javascript試作memento pattern時碰到一個難題,就是不知道怎麼做出friend宣告的效果,無法向Caretaker封裝Memento中存放狀態的細節。這幾天試了一下,其實適當地使用closure,就可以做出類似的效果。
測試的程式:
(function(){ var a; var b; var count=0; function c() { if(state===undefined) var state=new Array(); this.index = ++count; a = function(idx) { return state[idx]; } b = function(idx,_state) { state[idx] = _state; } } d = function() { var _c = new c(); this.e = function() { return a(_c.index); } this.f = function(_state) { b(_c.index,_state); } } })(); var g = new d(); var h = new d(); g.f(23); h.f(35); alert(g.e()); alert(h.e()); h.f(77); alert(g.e()); alert(h.e());
這裡用了一個匿名函數來做出closure,變數a與b由於宣告在匿名函數裡面,所以可以被c與d這兩個function使用。然後在c()裡面才定義a與b是兩個函數,由於位於c()裡面,所以透過a()與b()就可以存取定義在c()裡面的state變數。而a與b一開始是宣告在匿名函數裡面,所以d()也可以使用,透過迂迴的方式,就讓d()可以透過a()與b()存取定義在c()裡面的state變數。另外,由於c()是定義在匿名函數裡面,在匿名函數之外是無法存取的,而d()是在匿名函數內定義的global變數,所以可以使用。這樣設計好,就可以使用d()來把狀態存在c()的state變數中。
但是d()可以有很多instance,每一個instance如果都把狀態存在state變數裡面,那後面的存取動作就會蓋過前面的,所以在匿名函數裡面會維護一個計數器count,同時在c()開頭在沒定義state變數時才宣告state變數,然後讓每一個c()的instance會有一個獨立的index,在d()裡面需要透過這個index來存取state變數。
大致測試了一下,看起來可以working沒問題。
如果要用這個方法來做出memento pattern,那需要把c()(也就是memento)的instance存在Caretaker裡面,由於c()只有一個成員index,並沒有狀態,所以也可以說對Caretaker適當地把狀態封裝起來了。
接著改寫一下之前實作的memento pattern:
(function(){ var a; var b; var count = -1; var memento = function(state) { if(state===undefined) var state = new Array(); this.index = ++count; state[this.idx] = state; a = function(idx) { return state[idx]; } b = function(idx, _state) { state[idx] = _state; } } var _instance = null; getOriginator = function() { if(_instance==null) { _instance = new originator(); } return _instance; } var originator = function() { var _state = 0; var _memento=null; this.createMemento = function() { _memento = new memento(); b(_memento.index, _state); return _memento; } this.setMemento = function(obj) { if(obj instanceof memento) { _memento = obj; _state = a(_memento.index); } } this.getState = function() { return _state; } this.setState = function(state) { _state = state; } } })(); function caretaker() { var _memento = null; this.execute = function(state) { var _originator = getOriginator(); _memento = _originator.createMemento(); _originator.setState(state); alert(_originator.getState()); } this.unexecute = function() { var _originator = getOriginator(); _originator.setMemento(_memento); alert(_originator.getState()); } } var a = new caretaker(); a.execute(3); a.execute(5); a.unexecute();
跑完a.unexecute(),狀態有從5退回到3,看起來應該沒問題。