用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,看起來應該沒問題。