請求傳送者與接收者解耦——命令模式(五)

Liuwei-Sunny發表於2013-04-15

6 請求日誌

       請求日誌就是將請求的歷史記錄儲存下來,通常以日誌檔案(Log File)的形式永久儲存在計算機中。很多系統都提供了日誌檔案,例如Windows日誌檔案、Oracle日誌檔案等,日誌檔案可以記錄使用者對系統的一些操作(例如對資料的更改)。請求日誌檔案可以實現很多功能,常用功能如下:

       (1) “天有不測風雲”,一旦系統發生故障,日誌檔案可以為系統提供一種恢復機制,在請求日誌檔案中可以記錄使用者對系統的每一步操作,從而讓系統能夠順利恢復到某一個特定的狀態;

       (2) 請求日誌也可以用於實現批處理,在一個請求日誌檔案中可以儲存一系列命令物件,例如一個命令佇列;

       (3) 可以將命令佇列中的所有命令物件都儲存在一個日誌檔案中,每執行一個命令則從日誌檔案中刪除一個對應的命令物件,防止因為斷電或者系統重啟等原因造成請求丟失,而且可以避免重新傳送全部請求時造成某些命令的重複執行,只需讀取請求日誌檔案,再繼續執行檔案中剩餘的命令即可。

       在實現請求日誌時,我們可以將命令物件通過序列化寫到日誌檔案中,此時命令類必須實現java.io.Serializable介面。下面我們通過一個簡單例項來說明日誌檔案的用途以及如何實現請求日誌:

       Sunny軟體公司開發了一個網站配置檔案管理工具,可以通過一個視覺化介面對網站配置檔案進行增刪改等操作,該工具使用命令模式進行設計,結構如圖6所示:

網站配置檔案管理工具結構圖

       現在Sunny軟體公司開發人員希望將對配置檔案的操作請求記錄在日誌檔案中,如果網站重新部署,只需要執行儲存在日誌檔案中的命令物件即可修改配置檔案。

       本例項完整程式碼如下所示:

import java.io.*;
import java.util.*;

//抽象命令類,由於需要將命令物件寫入檔案,因此它實現了Serializable介面
abstract class Command implements Serializable {
	protected String name; //命令名稱
	protected String args; //命令引數
	protected ConfigOperator configOperator; //維持對接收者物件的引用
	
	public Command(String name) {
		this.name = name;
	}
	
	public String getName() {
		return this.name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public void setConfigOperator(ConfigOperator configOperator) {
		this.configOperator = configOperator;
	}
	
    //宣告兩個抽象的執行方法execute()
	public abstract void execute(String args);
	public abstract void execute();
}

//增加命令類:具體命令
class InsertCommand extends Command {
	public InsertCommand(String name) {
		super(name);
	}
	
	public void execute(String args) {
		this.args = args;
		configOperator.insert(args);
	}
	
	public void execute() {
		configOperator.insert(this.args);
	}
}

//修改命令類:具體命令
class ModifyCommand extends Command {
	public ModifyCommand(String name) {
		super(name);
	}
	
	public void execute(String args) {
		this.args = args;
		configOperator.modify(args);
	}
	
	public void execute() {
		configOperator.modify(this.args);
	}
}

//省略了刪除命令類DeleteCommand

//配置檔案操作類:請求接收者。由於ConfigOperator類的物件是Command的成員物件,它也將隨Command物件一起寫入檔案,因此ConfigOperator也需要實現Serializable介面
class ConfigOperator implements Serializable {
	public void insert(String args) {
		System.out.println("增加新節點:" + args);
	}
	
	public void modify(String args) {
		System.out.println("修改節點:" + args);
	}
	
	public void delete(String args) {
		System.out.println("刪除節點:" + args);
	}
}

//配置檔案設定視窗類:請求傳送者
class ConfigSettingWindow {
    //定義一個集合來儲存每一次操作時的命令物件
	private ArrayList<Command> commands = new ArrayList<Command>();
	private Command command; 

    //注入具體命令物件
	public void setCommand(Command command) {
		this.command = command;
	}
	
    //執行配置檔案修改命令,同時將命令物件新增到命令集合中
	public void call(String args) {
		command.execute(args);
		commands.add(command);
	}
	
    //記錄請求日誌,生成日誌檔案,將命令集合寫入日誌檔案
	public void save() {
		FileUtil.writeCommands(commands);
	}
	
    //從日誌檔案中提取命令集合,並迴圈呼叫每一個命令物件的execute()方法來實現配置檔案的重新設定
	public void recover() {
		ArrayList list;
		list = FileUtil.readCommands();
		
		for (Object obj : list) {
			((Command)obj).execute();
		}
	}
}

//工具類:檔案操作類
class FileUtil {
    //將命令集合寫入日誌檔案
	public static void writeCommands(ArrayList commands) {
		try {
			FileOutputStream file = new FileOutputStream("config.log");
			//建立物件輸出流用於將物件寫入到檔案中
    		ObjectOutputStream objout = new ObjectOutputStream(new BufferedOutputStream(file));
			//將物件寫入檔案
    		objout.writeObject(commands);
    		objout.close();
    		}
    	catch(Exception e) {
    			System.out.println("命令儲存失敗!");	
    			e.printStackTrace();
    	    }
	}
	
    //從日誌檔案中提取命令集合
	public static ArrayList readCommands() {
		try {
			FileInputStream file = new FileInputStream("config.log");
			//建立物件輸入流用於從檔案中讀取物件
    		ObjectInputStream objin = new ObjectInputStream(new BufferedInputStream(file));
			
			//將檔案中的物件讀出並轉換為ArrayList型別
    		ArrayList commands = (ArrayList)objin.readObject();
    		objin.close();
    		return commands;
    		}
    	catch(Exception e) {
    			System.out.println("命令讀取失敗!");
    			e.printStackTrace();
    			return null;	
    	    }		
	}
}

       編寫如下客戶端測試程式碼:

class Client {
	public static void main(String args[]) {
		ConfigSettingWindow csw = new ConfigSettingWindow(); //定義請求傳送者
		Command command; //定義命令物件
		ConfigOperator co = new ConfigOperator(); //定義請求接收者
		
        //四次對配置檔案的更改
		command = new InsertCommand("增加");
		command.setConfigOperator(co);
		csw.setCommand(command);
		csw.call("網站首頁");
		
		command = new InsertCommand("增加");
		command.setConfigOperator(co);
		csw.setCommand(command);
		csw.call("埠號");
		
		command = new ModifyCommand("修改");
		command.setConfigOperator(co);
		csw.setCommand(command);
		csw.call("網站首頁");
		
		command = new ModifyCommand("修改");
		command.setConfigOperator(co);
		csw.setCommand(command);		
		csw.call("埠號");
		
		System.out.println("----------------------------");
		System.out.println("儲存配置");
		csw.save();
			
		System.out.println("----------------------------");	
		System.out.println("恢復配置");
		System.out.println("----------------------------");	
		csw.recover();	
	}
}

       編譯並執行程式,輸出結果如下:

增加新節點:網站首頁

增加新節點:埠號

修改節點:網站首頁

修改節點:埠號

----------------------------

儲存配置

----------------------------

恢復配置

----------------------------

增加新節點:網站首頁

增加新節點:埠號

修改節點:網站首頁

修改節點:埠號

【作者:劉偉   http://blog.csdn.net/lovelion

相關文章