在應用程式設計過程中,一些特定作業需要能夠支援撤銷(undo),例如最近在寫的一個檔案管理系統。文件的一些基本操作如:rename、copy、cut等,需要支援undo、redo操作來提供更好的使用者體驗。總所周知,undo、redo操作需要兩個模式支撐:備忘錄模式(memento)保存物件操作資料狀態、指令模式(command)封裝使用者請求。結合起來可以提供良好的撤銷、重做操作。命令模式可以參考上面一篇文章點擊打開連結.下面主要說說備忘錄模式的實現,如有錯誤,不令賜教。
備忘錄模式主要有3位參與者:
a.保存狀態資訊的備忘錄物件(Memento)
b.產生狀態資訊的來源使用器(Originator) 的管理器
create_memento(); //保存对象状态信息创建一个备忘录返回 set_memento(Memento $mem); //根据传入的备忘录获取状态信息,恢复状态
set_state(State $state); //设备备忘录当前状态 get_state(); //获取备忘录当前状态備忘錄物件實作:
class Memento { private $state; public function get_state(){ return $this->state; } public function set_state(State $state){ $this->state = clone $state; } }set_state介面透過State參數提示確保參數類型的唯一性。要注意的是,PHP預設物件賦值的時候不會像C++那般執行拷貝建構函數,PHP是基於引用物件計數器,物件賦值預設計數器加一。這裡我們使用PHP提供的clone操作保證得到的是一個全新的物件。
源發器實作:
class Originator{ private $state; function _construct(){ $this->state = new State(); $this->state->set('action', 'create originator'); } function do_action1(){ $this->state->set('action', 'do_action 1'); } function do_action2(){ $this->state->set('action', 'do_action 2'); } function create_memento(){ $mem = new Memento(); $men->set_state($this->state); return $mem; } function set_memento(Memento $mem){ $this->state = $mem->get_state(); } function dump(){ echo $this->state->get('action') . "\n"; } }狀態資訊物件:
class State{ private $values = array(); public function set($key, $value){ $this->values[$key] = $value; } public function get($key){ if(isset($this->values[$key])){ return $this->values[$key]; } return null; } }最後是備忘錄管理器的簡單實作:
class CareTaker{ private $command; function __construct($cmd="Originator1"){ $this->command = $cmd; } private function do_execute(){ switch($this->command){ case 'Originator1':{ $action = new Originator(); $mem1 = $action->create_memento(); $action->dump(); $action->do_action1(); $mem2 = $action->create_memento(); $action->dump(); $action->do_action2(); $mem3 = $action->create_memento(); $action->dump(); //状态恢复 $action->set_memento($mem2); $action->dump(); $action->set_memento($mem1); $action->dump(); } } } }這裡有幾個地方可以改進的,首先,管理器應該提供一個隊列來管理一系列的備忘錄物件。其次客戶端請求命令用一個大大的switch-case做分發而不是利用命令模式把請求封裝為對象,導致了類別結構混亂。下一篇將實作一個完整版本的undo-redo操作。
The end.