1. 程式人生 > >《設計模式》學習筆記8——外觀模式

《設計模式》學習筆記8——外觀模式

定義

外觀模式引用書中的定義如下:

為子系統中的一組介面提供一個統一的入口。外觀模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。
外觀模式又稱為門面模式,它是一種物件結構型模式。外觀模式是迪米特法則的一種具體實
現,通過引入一個新的外觀角色可以降低原有系統的複雜度,同時降低客戶類與子系統的耦
合度。

理解

對於這個模式的理解,我覺得著重點在於幾個詞上,分別是一組介面統一入口降低複雜度
結合我們目前的系統進行說明,首先說什麼是一組介面。
在我們的系統中,由於業務需要,有三種日誌,分別是系統日誌、告警日誌、監控日誌,我用非正式的程式碼簡單表示如下:
系統日誌類介面定義:

/**
 * 日誌統一管理類,外觀類
 * 
 * @author tzx
 * @date 2018年1月4日
 */
public class LogHandle {
    private SysLogService sysLogService = new SysLogService();
    private AlarmLogService alarmLogService = new AlarmLogService();
    private MonitorLogService monitorLogService = new MonitorLogService();

    public void error(String msg, Object... args) {
        sysLogService.error(msg, args);
        alarmLogService.error(msg, args);
        monitorLogService.error(msg, args);
    }
}

告警日誌類介面定義:

/**
 * 告警日誌介面
 * 
 * @author tzx
 * @date 2018年1月4日
 */
public class AlarmLogService {
    private Logger sysLog = LoggerFactory.getLogger(this.getClass());

    public void error(String msg, Object... args) {
        String sys = "alarm";
        msg = "#" + msg + "#" + sys + "#";
        sysLog.error(msg, args);
    }
}

監控日誌類介面:

/**
 * 監控日誌介面
 * 
 * @author tzx
 * @date 2018年1月4日
 */
public class MonitorLogService {
    private Logger sysLog = LoggerFactory.getLogger(this.getClass());

    public void error(String msg, Object... args) {
        String sys = "monitor";
        msg = "|" + msg + "|" + sys + "|";
        sysLog.error(msg, args);
    }
}

上邊三個日誌介面為了表示功能的不同,簡單地對日誌內容進行了不同的拼接,實際專案中自然不會這樣。
這三個日誌介面,都是屬於日誌的操作,只是打印出的日誌內容被不同的系統採集再使用,而且他們基本上也都同時被使用到,所以可以說他們就是屬於外觀模式定義中的那一組介面
在沒有使用外觀模式的情況下,我們在業務程式碼中需要列印日誌的時候可能就會是下邊所示這樣:

/**
 * 部落格操作類
 * 
 * @author tzx
 * @date 2018年1月4日
 */
public class BlogService {
    private SysLogService sysLogService = new SysLogService();
    private AlarmLogService alarmLogService = new AlarmLogService();
    private MonitorLogService monitorLogService = new MonitorLogService();

    public void addBlog() {
        try{
        System.out.println("");
        }catch (Exception e) {
            sysLogService.error("系統異常{}", new Object[] { e.getMessage() });
            alarmLogService.error("系統異常{}", new Object[] { e.getMessage() });
            monitorLogService.error("系統異常{}", new Object[] { e.getMessage() });
        }
    }
}
/**
 * 使用者操作類
 * 
 * @author tzx
 * @date 2018年1月4日
 */
public class UserService {
    private SysLogService sysLogService = new SysLogService();
    private AlarmLogService alarmLogService = new AlarmLogService();
    private MonitorLogService monitorLogService = new MonitorLogService();

    public void addUser() {
        try {
            System.out.println("");
        } catch (Exception e) {
            sysLogService.error("系統異常{}", new Object[] { e.getMessage() });
            alarmLogService.error("系統異常{}", new Object[] { e.getMessage() });
            monitorLogService.error("系統異常{}", new Object[] { e.getMessage() });
        }
    }
}

很顯然,兩個service中有大量重複的程式碼。這裡只有兩個類列印日誌,就需要一共呼叫6次日誌列印方法,每多出一個類需要列印這樣的日誌,就會以3的倍數增加程式碼。
而且這裡的示例幾乎是最簡單的,在實際業務開發的時候可能就遠不止三行。
同時,像上邊這種做法,假如後續日誌系統增多,需要增加到四種甚至五種或者更多,那個每個列印日誌的類都需要進行相應的修改,很顯然,這是極不利於維護和拓展的。
因此,按照常規的思路,可能就會有兩種做法:
一種是把這種列印日誌的程式碼進行提取,封裝為一個方法進行呼叫。但是這種做法會存在一定的問題,首先,這個方法本身和業務是無關的,放在業務類中不合適。其次,除非所有需要列印日誌的類都有一個共同的父類,把這個方法放在父類中,否則就需要定義多次該方法。
於是乎,就有了第二種做法,那就是把這段程式碼提取到另外一個類中,那麼這個類就是所謂的外觀類了:

/**
 * 日誌統一管理類,外觀類
 * 
 * @author tzx
 * @date 2018年1月4日
 */
public class LogHandle {
    private SysLogService sysLogService = new SysLogService();
    private AlarmLogService alarmLogService = new AlarmLogService();
    private MonitorLogService monitorLogService = new MonitorLogService();

    public void error(String msg, Object... args) {
        sysLogService.error(msg, args);
        alarmLogService.error(msg, args);
        monitorLogService.error(msg, args);
    }
}

有了這樣一個類之後,我們的業務程式碼呼叫的時候就只需要一次就可以搞定,而這個管理日誌操作的類就是統一介面

/**
 * 使用者操作類
 * 
 * @author tzx
 * @date 2018年1月4日
 */
public class UserService {
    private LogHandle logHandle = new LogHandle();
    public void addUser() {
        try {
            System.out.println("");
        } catch (Exception e) {
            logHandle.error("系統異常{}", new Object[] { e.getMessage() });
        }
    }
}

與此同時,如果後續日誌系統變化,不論是增加新的日誌型別,還是刪減舊的日誌型別,都只需要改動logHandle類就可以了,而且每個呼叫日誌的地方程式碼也都減少了很多,這就大大降低了系統的複雜度

要點

那麼根據上邊的理解和示例,外觀模式有如下一些要點:
1. 需要有一組介面,他們幾乎都是成套被使用;
2. 一個外觀類或介面,實際操作上邊的一組介面,而提供一個公開的介面方法供外部呼叫;
3. 客戶端只需要一次呼叫外觀類的介面,而不需要分別呼叫最開始那一組介面的每一個。

在沒有學這個模式的時候,我覺得單例模式是最簡單的設計模式,而現在來看,這個似乎才是最簡單的設計模式。