軟體項目實訓及課程設計指導——如何應用觀察者設計模式重構系統中的日誌處理功能實現的程序代碼
1、GOF設計模式中的觀察者設計模式
(1)什麼是觀察者設計模式
GOF設計模式中的觀察者設計模式定義了一種解耦「一對多」的依賴關係的編程模式,讓多個不同的觀察者對象同時監聽某一個主題對象。當這個主題對象在「狀態」發生變化時,會通知所有觀察者對象,使這些觀察者對象能夠自動地更新自己的行為以響應主題對象的「狀態」變化。
(2)在示例項目中應用觀察者設計模式的目的
觀察者設計模式本身是對面向對象OOP程序類設計時的「單一職責」設計原則的具體應用——觀察者和被觀察者對象各自完成自己的功能實現方面的不同的職責,兩者不需要緊密耦合和產生複合的職責。
在示例項目銀行帳戶信息管理系統中,作者應用觀察者設計模式的主要目的是希望能夠達到分離「事件的產生者」和「事件的響應者」之間關係的設計目標——也就是將系統中的日誌記錄功能(包括異常日誌和交易日誌)從具體的業務功能實現類或者數據訪問功能的DAO組件類中分離出來。
應用這樣的設計和實現方案,一方面不僅能夠減少系統中的日誌記錄功能實現代碼的「重複」,另一方面也能夠提高軟體應用系統項目在日誌記錄功能實現方面的可擴展性。
讀者可以從作者的另一篇文章《軟體項目實訓及課程設計指導——如何應用策略設計模式的思想設計通用的資料庫連接類》一文中所示的黑體部分的代碼中能夠了解到常規的日誌記錄功能實現所存在的問題——直接將日誌記錄功能實現的代碼插入到資料庫連接ConnectDBBean類的功能實現代碼中,直接包含有日誌處理功能實現的程序代碼。
2、如何在示例項目中應用觀察者設計模式
有關觀察者設計模式的具體編程實現及代碼示例,請讀者參考作者的「J2EE項目實訓——UML及設計模式」一書(如下示圖為該書的封面)中的第10章「典型GOF設計模式及應用」中的有關內容。
作者在下文為讀者介紹如何將觀察者設計模式應用於構建軟體應用系統持久層資料庫連接Connection對象實例的功能實現代碼中以進一步提高軟體應用系統項目中的程序代碼的可擴展性。
下面以重構作者的另一篇文章《軟體項目實訓及課程設計指導——如何應用策略設計模式的思想設計通用的資料庫連接類》一文中的ConnectDBBean類的日誌記錄功能實現過程為示例,說明在軟體應用系統項目中如何具體地應用觀察者設計模式優化程序代碼的功能實現。
3、設計和實現觀察者設計模式中的被觀察者程序類的代碼
在觀察者設計模式中的被觀察者程序類代表需要監控的業務功能類,在本示例中也就是資料庫連接類ConnectDBBean。依據觀察者設計模式中的程序類之間的關係,資料庫連接ConnectDBBean類作為被觀察者類,應該繼承於java.util 包中的Observable類、並利用addObserver方法關聯觀察者類和用notifyObservers方法觸發通知事件。
重構後的完整功能實現的代碼請見如下的程序代碼示例,並請讀者注意其中的黑體部分所標識的語句代碼——對ConnectDBBean類重構後的功能實現程序代碼示例。
package com.px1987.webbank.dao.imple;
import java.sql.*;
import java.util.Observable;
import com.px1987.webbank.config.ClassNameConfig;
import com.px1987.webbank.dao.inter.ConnectDBInterface;
import com.px1987.webbank.exception.WebBankException;
public class ConnectDBBean extends Observableimplements ConnectDBInterface{
String JDBC_DBDriver_ClassName= null;
String JDBC_DSN_URL = null;
String JDBC_dbUserName=null;
String JDBC_dbUserPassWord=null;
private java.sql.Connection con = null;
public ConnectDBBean() throws WebBankException {
/*利用ClassNameConfig實現從屬性文件中獲得與資料庫相關的連接信息 */
JDBC_DBDriver_ClassName =
ClassNameConfig.getProperty("JDBC_DBDriver_ClassName");
JDBC_DSN_URL = ClassNameConfig.getProperty("JDBC_DSN_URL");
JDBC_dbUserName = ClassNameConfig.getProperty("JDBC_dbUserName");
JDBC_dbUserPassWord = ClassNameConfig.getProperty("JDBC_dbUserPassWord");
try {
Class.forName(JDBC_DBDriver_ClassName);
}
catch (java.lang.ClassNotFoundException e) {
intlogImpleKind=
Integer.parseInt(ClassNameConfig.getProperty("logImpleKind"));
this.addObserver(LogInfoFactory.newLogInstance(logImpleKind));this.setChanged();
this.notifyObservers(e);
throw new WebBankException("不能正確地加載JDBC驅動程序");
}
try {
con = DriverManager.getConnection(JDBC_DSN_URL,JDBC_dbUserName,
JDBC_dbUserPassWord);
}
catch (java.sql.SQLException e) {
intlogImpleKind=
Integer.parseInt(ClassNameConfig.getProperty("logImpleKind"));
this.addObserver(LogInfoFactory.newLogInstance(logImpleKind));this.setChanged();
this.notifyObservers(e);
throw new WebBankException("不能正確地連接資料庫"+
"並且出現SQLException");
}
catch (NullPointerException e){
intlogImpleKind=
Integer.parseInt(ClassNameConfig.getProperty("logImpleKind"));
this.addObserver(LogInfoFactory.newLogInstance(logImpleKind));this.setChanged();
this.notifyObservers(e);
throw new WebBankException("無法連接資料庫"+
"並且出現NullPointerException異常");
}
}
public void closeDBCon() throws WebBankException {
try {
con.close();
con = null;
}
catch (SQLException e){
intlogImpleKind=
Integer.parseInt(ClassNameConfig.getProperty("logImpleKind"));
this.addObserver(LogInfoFactory.newLogInstance(logImpleKind));this.setChanged();
this.notifyObservers(e);
throw new WebBankException("不能正確地關閉資料庫連接");
}
}
public Connection getConnection() throws WebBankException {
return con;
}
}
其中的LogInfoFactory類為創建日誌功能實現類對象實例的一個工廠方法模式的工廠類。本工廠類中的產品對象實例的創建方法與標準的GOF工廠設計模式中的產品對象實例的創建方法不同,主要是由於該日誌功能實現類是作為觀察者模式的觀察者類。它返回的是Observer接口類型的對象實例——並實現將觀察者類與被觀察者類相互關聯,而不是日誌功能ExceptionLogInterface接口的實現類LogObserver的對象實例。
當然,為了節省本文章的篇幅,作者在本文中沒有將該LogInfoFactory類的程序代碼一併附錄出。
4、設計和實現觀察者設計模式中的觀察者類的代碼
在觀察者設計模式中的觀察者類代表響應業務功能類的狀態變化的非業務功能類的代碼,在本示例中也就是對資料庫連接類ConnectDBBean中的異常拋出信息進行日誌記錄的功能處理類。
(1)添加觀察者類LogObserver所需要實現的ExceptionLogInterface接口
在MyEclipse開發工具中為本示例項目添加觀察者類LogObserver所需要實現的接口ExceptionLogInterface,該接口所在的包名稱為com.px1987.webbank.util。然後再設計該ExceptionLogInterface接口中的各個功能方法。最後的設計結果請見下圖所示,在該接口中目前只聲明一個logInfo方法——作者對該接口進行了簡化以節省本文的篇幅。
(2)設計觀察者模式中的觀察者類LogObserver
在MyEclipse開發工具中為本項目添加觀察者類LogObserver,該觀察者類一方面需要實現java.util包中的Observer接口,另一方面也需要實現前面的ExceptionLogInterface接口,並且對拋出的異常信息進行觀察監控。
該類所在的包名稱為com.px1987.webbank.util,為了簡化本示例的實現代碼。目前該類的功能實現是基於JDK的Logger類實現具體的日誌功能實現。然後再編程實現該功能類,最後的功能實現代碼請見如下程序代碼示例所示。並請讀者注意其中的黑體部分所標識的語句代碼——觀察者類LogObserver的代碼示例。
package com.px1987.webbank.util;
import java.util.*;
import java.util.logging.*;
public class LogObserver implements Observer, ExceptionLogInterface {
public LogObserver() {
}
public void update(Observable o, Object arg){
String logInfo=o.getClass().getName()+"類出現了"+
arg.getClass().getName()+"類型的異常,並且異常內容為:"+
((Exception)arg).getMessage();
logInfo(logInfo);
}
public void logInfo(String logInfoText) {
Logger logger= Logger.getLogger(this.getClass().getName());
logger.log(Level.INFO, logInfoText);
}
}
5、再執行TestConnectDBBean測試用例類代碼以驗證代碼重構的正確性
為了能夠拋出異常信息以驗證觀察者設計模式重構的正確性,在測試過程中,作者故意關閉MySQL資料庫伺服器。然後再執行TestConnectDBBean測試用例類的程序代碼,此時在控制臺中將出現日誌信息的輸出文字,請見下圖所示示例圖中的紅色提示文字。
根據上圖所示的執行結果,表明採用觀察者設計模式重構的日誌記錄功能實現的代碼是有效的、而且也是正確的。這為軟體應用系統項目以後的日誌記錄功能實現的技術及應用的要求改變提供了良好的靈活性——因為日誌記錄具體的功能實現的程序代碼已經從軟體應用系統的業務功能實現代碼中分離出來了。
再次啟動MySQL資料庫伺服器,並再次執行TestConnectDBBean測試用例類的代碼,此時在控制臺中將不再出現日誌信息的輸出文字,請見下圖示例圖所示。表明觀察者模式中的觀察者類的監控邏輯也是有效的,沒有出現誤判斷的現象。
但希望讀者要注意觀察者設計模式在應用中也仍然還存在有一定的問題,儘管本項目示例採用這樣的方法能夠為日誌處理帶來良好的可擴展性和靈活性,但仍然有一定的代碼重複——下面的程序代碼將重複地出現在不同的功能方法中,最好的實現方法應該是採用面向切面編程AOP技術徹底地進行分離和進一步地減少程序代碼的重複性。
int logImpleKind=Integer.parseInt(ClassNameConfig.getProperty("logImpleKind"));
this.addObserver(LogInfoFactory.newLogInstance(logImpleKind));
/** 注意要設置變化點 */
this.setChanged();
this.notifyObservers(e);
如何應用策略設計模式的思想設計通用的資料庫連接類
如何正確地創建和銷毀軟體應用系統中JDBC資料庫連接對象實例
如何正確地創建和銷毀軟體應用系統中網絡通訊中的Socket對象實例
在程序中如何正確地創建和銷毀軟體應用系統中文件IO流對象實例
課程設計指導——如何應用Java反射技術靈活地創建程序類對象實例