1. 程式人生 > >重做(redo)和撤銷(undo)的完整實現

重做(redo)和撤銷(undo)的完整實現

   undo-redo需要備忘錄模式和命令模式做支撐,之前有學習過了command模式和memento模式的一些基本知識。這裡要結合兩個模式實現一個undo-redo操作的模組,鞏固所學的知識。

系統框圖:

    

     命令分發控制器主要有四個任務:
    1.系統初始化,載入系統配置引數並把這些資料快取起來,這些應用程式級別的配置引數可以使用序列化機制,把資料快取而不用每次去讀取檔案,加快訪問效率。
    2.根據前端請求,收集引數生成一個請求(Request)。
    3.把請求對映到具體業務邏輯的命令模組(Command)。
    4.執行操作並把結果返回給前端檢視。

    業務邏輯層根據傳入的context物件可以獲取執行引數,執行完畢後還可以把執行結果通過context物件返回給上一層。

    命令分發控制器的實現:

class Controller{

	private function __construct() {}

	static function run(){
		$instance = new Controller();
		$instance->init();
		$instance->handleRequest();
	}
	function init(){
		$application
			= \base\ApplicationHelper::instance();
		$application->system_init();
	}
	function handleRequest(){
		$request  = new \controller\Request();
		$cmd_r = new \command\CommandResolver();
		$cmd = $cmd_r->get_command($request);
		$cmd->execute($request);
	}
}
     通過把建構函式宣告為private,controller為一個單例。

     對於類似PHP這樣的解釋型的語言,要實現undeo/redo機制,必須用到一些快取機制(session)來儲存命令執行的歷史記錄。這裡的session模組主要負責維護一個命令歷史記錄,其實現如下:

namespace base;

require_once('session_registry.php');

class SessionMementoTaker extends SessionRegistry{
	
	const 	COMMAND_COUNT = 5;
	private $persent = 0;
	private $cmd_stack = array();
	static public function instance(){
		return parent::instance();
	}	

	public function push_command(Command $cmd){
		
		$this->cmd_stack = self::instance()->get('cmd_stack');	
		if(!empty($this->cmd_stack)){
			if(count($this->cmd_stack) >self::COMMAND_COUNT){
				array_shift($this->cmd_stack);
				reset($this->cmd_stack);
			}
		} 
		array_push($this->cmd_stack, $cmd);
		$this->persent = count($this->cmd_stack) + 1;
		self::instance()->set('cmd_stack', $this->cmd_stack);
		self::instance()->set('cmd_persent', $this->persent);
	}	

	public function get_undo_command(){
		$this->persent = self::instance()->get('cmd_persent');	
		$this->cmd_stack = self::instance()->get('cmd_stack');	
		if(!empty($this->cmd_stack) && $this->persent > 0){
			$command = $this->cmd_stack[--$this->persent];
			self::instance()->set('cmd_persent', $this->persent);
			return $command;
		}	
		return null;
	}	
	public function get_redo_command(){
		$this->persent = self::instance()->get('cmd_persent');	
		$this->cmd_stack = self::instance()->get('cmd_stack');	
		if(!empty($this->cmd_stack) && $this->persent < count($this->cmd_stack)){
			$command = $this->cmd_stack[$this->persent++];
			self::instance()->set('cmd_persent', $this->persent);
			return $command;
		}	
		return null;
	}
}

    SessionMementoTaker的實現基於之前實現的一個會話(登錄檔模式)。根據cookies裡面儲存的會話ID恢復不同的物件資料,可以達到同一使用者多次請求訪問同一物件資料的目的。SessionMementoTaker額外提供了三個介面,push_command操作新增命令到歷史命令列表。歷史命令列表最大長度為5個,超過5個把最開始的命令移除。另外,push_command相當於新增一個新的命令,要把命令指標(persent)移動到最新的位置。捨棄之前的狀態。get_undo_command獲取最後一次執行的歷史命令並更新指標,get_redo_command同理。
    歷史命令列表: command1---command2---command3---* 星號表示persent,指向最新要執行的命令。
    一次undo操作:command1---command2-*--command3--- 回滾之後persent指標往後移動。
    一次undo操作:command1--*command2----command3--- 回滾之後persent指標往後移動。
    一次redo操作:command1---command2-*--command3--- 重做之後persent指標往前移動。
    push_command:command1---command2---command3---command4---* persent更新到最前端

    在這裡把單個command物件看成是一個原發器(Originator)。根據需要主動建立一個備忘錄(memento)儲存此刻它的內部狀態,並把command物件放入歷史命令記錄列表。

    command基類實現:

namespace woo\command;

require_once('../memento/state.php');
require_once('../memento/memento.php');

abstract class Command {
	
	protected $state;
	final function __construct(){
		$this->state = new \woo\memento\State();
	}
	
	function execute(\woo\controller\Request $request) {
		$this->state->set('request', $request);
		$this->do_execute($request);
	}
	
	abstract function do_execute(\woo\controller\Request $request);
	function do_unexecute(\woo\controller\Request $request) {}
	
	public function get_state(){
		return $this->state;
	}
	
	public function set_state(State $state){
		$this->state = $state;	
	}

	public function get_request(){
		if(isset($this->state)){
			return $this->state->get('request');
		}
		return null;
	}
	
	public function set_request(\woo\controller\Request $request){
		if(isset($this->state)){
			return $this->state->set('request', $request);
		}
	}

	public function create_memento(){
		\woo\base\SessionMementoTaker::push_command($this);
		$mem = new \woo\memento\Memento();
		$mem->set_state($this->state);
        return $mem;
	}

	public function set_memento(Memento $mem){
		$this->state = $mem->get_state();
	}
}

    命令任務在執行開始的時候儲存請求命令的引數,在命令執行過程中還可以儲存其他必要的引數。由於有些命令不支援撤銷操作所以在父類實現裡一個空的unexecute;

    儲存命令狀態的物件:

class State{
	
	private $values = array();

	function __construct(){
			
	}
	
	public function set($key, $value){
		$this->values[$key] = $value;
	}
	
	public function get($key){
		if(isset($this->values[$key]))
		{
			return $this->values[$key];
		}
		return null;
	}
}

一個支援undo-redo的複製檔案的命令:
namespace woo\command;

require_once('request.php');
require_once('command.php');
require_once('../base/registry.php');
require_once('../file_manager.php');
require_once('../base/session_memento.php');

class CopyCommand extends Command {
	function do_execute(\controller\Request $request) {
		$src_path = $request->get_property('src');
		$dst_path = $request->get_property('dst');
		$this->state->set('src_path', $src_path);
		$this->state->set('dst_path', $dst_path);
		$this->create_memento();
		$file_manager = \base\Registry::file_manager();
		$ret = $file_manager->copy($src_path, $dst_path);
		$request->add_feedback($ret);
		//...
	}
}
    命令物件要做的工作比較單一:獲取引數(校驗引數),儲存必要的狀態資訊,把控制權交給具體的業務邏輯物件。新增執行結果並返回。不同的命令需要不同的請求引數,一些命令根本不需要也不支援撤銷操作,所以可以選擇性的執行create_memento操作。

    最後是要實現的undo-redo,在這裡我把undo/redo也看成是一次普通的命令請求,而不需要在控制器做額外的分發處理。


撤銷命令:

namespace woo\command;

require_once('request.php');
require_once('command.php');
require_once('../base/registry.php');
require_once('../base/session_memento.php');

class UndoCommand extends Command{
	public function do_execute(\controller\Request $request){
		$command = \base\SessionMementoTaker::get_undo_command();
		if(isset($command)){
			$old_req = $command->get_request();
			$command->do_unexecute($old_req);
			$request->set_feedback($old_req->get_feedback());
		} else{
			$request->add_feedback('undo command not fount');
		}
		return;
	}
}

重做命令:
namespace woo\command;

require_once('request.php');
require_once('command.php');
require_once('../base/registry.php');

class RedoCommand extends Command {
	public function do_execute(\woo\controller\Request $request){
		$command = \woo\base\SessionMementoTaker::get_redo_command();
		if(isset($command)){
			$old_req = $command->get_request();
			$command->do_execute($old_req);
			$request->set_feedback($old_req->get_feedback());
		} else{
			$request->add_feedback('undo command not fount');
		}
		return;
	}
}

The end.

相關推薦

(redo)撤銷(undo)的完整實現

   undo-redo需要備忘錄模式和命令模式做支撐,之前有學習過了command模式和memento模式的一些基本知識。這裡要結合兩個模式實現一個undo-redo操作的模組,鞏固所學的知識。 系統框圖:           命令分發控制器主要有四個任務:     1.

數據庫的備份與還原系列——單表備份恢復詳細完整實現

單表備份 單表還原 表定義備份還原 表數據備份還原 參考實現:https://www.percona.com/doc/percona-xtrabackup/LATEST/innobackupex/innobackupex_script.htmlRestoring Individual Tabl

6. Oracle 回滾(ROLLBACK)撤銷(UNDO)

切換 ... nbsp 實現 再次 oba orm 分鐘 employees 轉載自:http://blog.csdn.net/leshami/article/details/5731158 一、回滾(ROLLBACK)和撤銷(UNDO) 回滾和前滾是保證Oracle

安卓開發實戰之app之版本更新升級(DownloadManagerhttp下載)完整實現

前言 本文將講解app的升級與更新。一般而言使用者使用App的時候升級提醒有兩種方式獲得: 一種是通過應用市場 獲取 一種是開啟應用之後提醒使用者更新升級 而更新操作一般是在使用者點選了升級按鈕之後開始執行的,這裡的升級操作也分為兩種形式: 一般升

第十二章:日誌歸檔

關系數據庫 數據庫管理系統 當前 維護 height 進程 dba ren 情況 [大綱]? 重做日誌文件、歸檔日誌文件的結構?重做日誌文件、歸檔日誌文件工作過程? 管理重做日誌文件、歸檔日誌一、在線重做日誌文件1.重做日誌概述 在數據庫的使用過程中,可能會

Android:基於EditText實現撤銷機制

一、 場景描述和思路分析 說到撤銷和重做想必大家腦海中浮現的一定是Ctrl+Z、Ctrl+Y這兩個快捷鍵,平常生產開發的時候也少不了要和這兩個按鍵打交道。作為一個開發者筆者自然對其中的實現方法感到好奇,想必閱讀此文的你也是一樣的。 如果

Command(命令)——物件行為型模式(通過Command設計模式實現WinForm表單維護的撤銷功能)

Command(命令)——物件行為型模式(通過Command設計模式實現WinForm表單維護的撤銷與重做功能) 意圖 動機 典型場景 程式碼實現 ICommand介面,定義execute和undo操作 OperationCom

【程式設計模式】(一) ------ 命令模式 ” 及 “撤銷

前言 本文及以後該系列的篇章都是本人對 《遊戲程式設計模式》這本書的閱讀理解,從中對一些原理,用更直白的語言描述出來,並對部分思路或功能進行初步實現。而本文所描述的 命令模式, 相信讀者應該都有了解過或聽說過,如果尚有疑惑的讀者,我希望本文能對你有所幫助。 命令模式是設計模式中的一種,但該系列所指的程式設計模

MySQL中的日誌(redo log),回滾日誌(undo log),以及二進位制日誌(binlog)的簡單總結

MySQL中有六種日誌檔案, 分別是:重做日誌(redo log)、回滾日誌(undo log)、二進位制日誌(binlog)、錯誤日誌(errorlog)、慢查詢日誌(slow query log)、一般查詢日誌(general log),中繼日誌(relay log)。 其中重做日誌和回滾日誌與

Android簡單塗鴉以及撤銷實現方法

前段時間研究了下塗鴉功能的實現,其實單獨的塗鴉實現起來還是挺簡單的,關鍵的技術難點是撤銷與重做功能的實現。但是這裡暫時只說明下塗鴉功能的實現,高手勿噴哈,而且該功能在Android SDK提供的APIDemo當中就有的,但是如果能夠將該地方的知識點搞懂的話,我認為View

CSS3實現五子棋Web小遊戲,Canvas畫布DOM兩種實現,並且具有悔棋撤銷悔棋功能。

posit oct padding 角色 sar pac osi fse ech 用Canvas實現五子棋的思路: 1、點擊棋盤,獲取坐標x,y,計算出棋子的二維數組坐標i和j, 2、棋子的實現,先arc一個圓,再填充漸變色。 3、下完一步棋後切換畫筆和角色。 4、贏法算法

簡單記錄一次REDO文件損壞報錯 ORA-00333日誌讀取塊出錯

clas 後者 利用 實例恢復 poi cancel true cover html 一.故障描寫敘述 首先是實例恢復須要用到的REDO文件損壞 二、解決方法 1.對於非當前REDO或者當前REDO可是無活動事務使用下面CLEAR命令: 用CLEAR命令重建該日誌

Oracle Logminer 分析日誌RedoLog歸檔日誌ArchiveLog

cti data 格式 保存 命令 重啟 msl dba object 在實際開發過程中,有時我們很有可能需要某個表的操作痕跡,或通過記錄的SQL語句進行有目的性的數據恢復(此時POINT-IN-TIME恢復已經滿足不了更細的粒度)、或僅僅是查看;

修改日誌文件(redo log)大小

inactive 訪問 結束 過渡 操作 div check 日誌組 soft 重做日誌相關數據字典 1、v$log 記錄數據庫中有多少個重做日誌組,每個組中有多少個成員、日誌大小及狀態 2、v$logfile 記錄著每個日誌組成員的屬性、文件路徑、文件名、狀態

資料庫日誌redoundo

資料庫的ACID屬性 Atomicity:原子性,以事物transact為最小單位,事物中的所有操作,要麼都執行完,要麼都不執行,不存在一部分操作執行,另一部分操作不執行的情況。 Consistency:一致性,在事物開始和事物完成後,資料庫的完整性限制不會改變。 Isolation:隔離性,同一個資料

資料庫的REDOUNDO機制

UNDO和REDO機制 redo undo機制是在資料庫引擎曾實現的。 undo機制 UNDO機制如下: 假設有A、B兩個資料,值分別為1,2。 A.事務開始. B.記錄A=1到undo log. C.修改A=3. D.記錄B=2到undo log. E.修改B=4. F.將un

撤銷回退的實現

功能點描述 大家應該都使用過瀏覽器的後退和前進功能,它就是我們今天的主題:撤銷和回退。其實不光是在瀏覽器裡面,在眾多的工具軟體內也都有類似的功能,遠的不說,例如:vscode、ppt。 實現思路 在說實現思路之前,我先上一張圖: 眼尖的同學可能已經看到了 Stack ,是的,你想的沒錯,

C# Command命令(行為型模式)+佇列 實現事務,帶非同步命令試機制生命週期

一、簡介 耦合是軟體不能抵禦變變化的根本性原因,不僅實體物件與實體物件之間有耦合關係(如建立性設計模式存在的原因),物件和行為之間也存在耦合關係.   二、實戰 1、常規開發中,我們經常會在控制器中或者Main方法中呼叫多個物件,進行批量的操作(完成一次事務性的操作),像下面這樣:

網站定向 301302定向的php實現

內容來源自我的部落格:http://www.jcsoo.com/wordpress/?p=15 前幾天搭建好了wordpress的部落格,但是我發現了一個問題,就是我的部落格在wordpress的資料夾(www.jcsoo.com/wordpress)才可以訪問,我想

ESP分割槽MSR分割槽下怎麼GHOST系統

先科普一下兩個分割槽: EFI系統分割槽,即 EFI system partition,簡寫為 ESP。ESP 是一個 FAT16 或 FAT32 格式的物理分割槽,但是其分割槽標識是 EF (十六進