前言
本文的主要內(nèi)容:
- 介紹外觀模式
- 示例
- 自己泡茶
- 到茶館喝茶
- 外觀模式總結(jié)
- 外觀模式的典型應(yīng)用
- spring JDBC 中的外觀模式
- Mybatis中的外觀模式
- Tomcat 中的外觀模式
- SLF4J 中的外觀模式
外觀模式
外觀模式是一種使用頻率非常高的結(jié)構(gòu)型設(shè)計(jì)模式,它通過引入一個(gè)外觀角色來簡(jiǎn)化客戶端與子系統(tǒng)之間的交互,為復(fù)雜的子系統(tǒng)調(diào)用提供一個(gè)統(tǒng)一的入口,降低子系統(tǒng)與客戶端的耦合度,且客戶端調(diào)用非常方便。
外觀模式又稱為門面模式,它是一種對(duì)象結(jié)構(gòu)型模式。外觀模式是迪米特法則的一種具體實(shí)現(xiàn),通過引入一個(gè)新的外觀角色可以降低原有系統(tǒng)的復(fù)雜度,同時(shí)降低客戶類與子系統(tǒng)的耦合度。
外觀模式包含如下兩個(gè)角色:
Facade(外觀角色):在客戶端可以調(diào)用它的方法,在外觀角色中可以知道相關(guān)的(一個(gè)或者多個(gè))子系統(tǒng)的功能和責(zé)任;在正常情況下,它將所有從客戶端發(fā)來的請(qǐng)求委派到相應(yīng)的子系統(tǒng)去,傳遞給相應(yīng)的子系統(tǒng)對(duì)象處理。
SubSystem(子系統(tǒng)角色):在軟件系統(tǒng)中可以有一個(gè)或者多個(gè)子系統(tǒng)角色,每一個(gè)子系統(tǒng)可以不是一個(gè)單獨(dú)的類,而是一個(gè)類的集合,它實(shí)現(xiàn)子系統(tǒng)的功能;每一個(gè)子系統(tǒng)都可以被客戶端直接調(diào)用,或者被外觀角色調(diào)用,它處理由外觀類傳過來的請(qǐng)求;子系統(tǒng)并不知道外觀的存在,對(duì)于子系統(tǒng)而言,外觀角色僅僅是另外一個(gè)客戶端而已。
外觀模式的目的不是給予子系統(tǒng)添加新的功能接口,而是為了讓外部減少與子系統(tǒng)內(nèi)多個(gè)模塊的交互,松散耦合,從而讓外部能夠更簡(jiǎn)單地使用子系統(tǒng)。
外觀模式的本質(zhì)是:封裝交互,簡(jiǎn)化調(diào)用。
示例
泡茶需要水 Water
public class Water {
private int temperature; // 溫度
private int capacity; // 容量
public Water() {
this.temperature = 0;
this.capacity = 10;
}
// 省略...
}
泡茶需要茶葉 TeaLeaf
public class TeaLeaf {
private String teaName;
// 省略...
}
燒水需要用水壺?zé)?,將水加?/p>
public class KettleService {
public void waterBurning(String who, Water water, int burnTime) {
// 燒水,計(jì)算最終溫度
int finalTermperature = Math.min(100, water.getTemperature() + burnTime * 20);
water.setTemperature(finalTermperature);
System.out.println(who + " 使用水壺?zé)罱K水溫為 " + finalTermperature);
}
}
泡茶,將燒好的水與茶葉進(jìn)行沖泡,最終得到一杯茶水
public class TeasetService {
public Teawater makeTeaWater(String who, Water water, TeaLeaf teaLeaf) {
String teawater = "一杯容量為 " + water.getCapacity() + ", 溫度為 " + water.getTemperature() + " 的" + teaLeaf.getTeaName() + "茶水";
System.out.println(who + " 泡了" + teawater);
return new Teawater(teawater);
}
}
人喝茶水
public class Man {
private String name;
public Man(String name) {
this.name = name;
}
public void drink(Teawater teawater) {
System.out.println(name + " 喝了" + teawater.getTeaWater());
}
}
自己泡茶喝
張三、李四各自泡茶喝,各自都需要準(zhǔn)備茶具、茶葉、水,各自還要完成燒水、泡茶等操作
public class Main {
public static void main(String[] args) {
Man zhangsan = new Man("張三");
KettleService kettleService1 = new KettleService();
TeasetService teasetService1 = new TeasetService();
Water water1 = new Water();
TeaLeaf teaLeaf1 = new TeaLeaf("西湖龍井");
kettleService1.waterBurning(zhangsan.getName(), water1, 4);
Teawater teawater1 = teasetService1.makeTeaWater(zhangsan.getName(), water1, teaLeaf1);
zhangsan.drink(teawater1);
System.out.println();
Man lisi = new Man("李四");
KettleService kettleService2 = new KettleService();
TeasetService teasetService2 = new TeasetService();
Water water2 = new Water(10, 15);
TeaLeaf teaLeaf2 = new TeaLeaf("碧螺春");
kettleService2.waterBurning(lisi.getName(), water2, 4);
Teawater teawater2 = teasetService2.makeTeaWater(lisi.getName(), water2, teaLeaf2);
lisi.drink(teawater2);
}
}
輸出為
張三 使用水壺?zé)?,最終水溫為 80
張三 泡了一杯容量為 10, 溫度為 80 的西湖龍井茶水
張三 喝了一杯容量為 10, 溫度為 80 的西湖龍井茶水
李四 使用水壺?zé)罱K水溫為 90
李四 泡了一杯容量為 15, 溫度為 90 的碧螺春茶水
李四 喝了一杯容量為 15, 溫度為 90 的碧螺春茶水
自己泡茶喝模式圖

到茶館喝茶
茶館,茶館有不同的套餐
public class TeaHouseFacade {
private String name;
private TeasetService teasetService;
private KettleService kettleService;
public TeaHouseFacade(String name) {
this.name = name;
this.teasetService = new TeasetService();
this.kettleService = new KettleService();
}
public Teawater makeTea(int teaNumber) {
switch (teaNumber) {
case 1:
Water water1 = new Water();
TeaLeaf teaLeaf1 = new TeaLeaf("西湖龍井");
kettleService.waterBurning(this.name, water1, 4);
Teawater teawater1 = teasetService.makeTeaWater(this.name, water1, teaLeaf1);
return teawater1;
case 2:
Water water2 = new Water(10, 15);
TeaLeaf teaLeaf2 = new TeaLeaf("碧螺春");
kettleService.waterBurning(this.name, water2, 4);
Teawater teawater2 = teasetService.makeTeaWater(this.name, water2, teaLeaf2);
return teawater2;
default:
Water water3 = new Water();
TeaLeaf teaLeaf3 = new TeaLeaf("招牌烏龍");
kettleService.waterBurning(this.name, water3, 5);
Teawater teawater3 = teasetService.makeTeaWater(this.name, water3, teaLeaf3);
return teawater3;
}
}
}
張三和李四點(diǎn)茶,只需要告訴茶館套餐編號(hào)即可,水、茶葉由茶館準(zhǔn)備,燒水泡茶的操作由茶館統(tǒng)一完成
public class Test {
public static void main(String[] args) {
TeaHouseFacade teaHouseFacade = new TeaHouseFacade("老舍茶館");
Man zhangsan = new Man("張三");
Teawater teawater = teaHouseFacade.makeTea(1);
zhangsan.drink(teawater);
System.out.println();
Man lisi = new Man("李四");
Teawater teawater1 = teaHouseFacade.makeTea(2);
lisi.drink(teawater1);
}
}
輸出為
老舍茶館 使用水壺?zé)?,最終水溫為 80
老舍茶館 泡了一杯容量為 10, 溫度為 80 的西湖龍井茶水
張三 喝了一杯容量為 10, 溫度為 80 的西湖龍井茶水
老舍茶館 使用水壺?zé)?,最終水溫為 90
老舍茶館 泡了一杯容量為 15, 溫度為 90 的碧螺春茶水
李四 喝了一杯容量為 15, 溫度為 90 的碧螺春茶水
到茶館喝茶模式圖

外觀模式總結(jié)
外觀模式的主要優(yōu)點(diǎn)如下:
- 它對(duì)客戶端屏蔽了子系統(tǒng)組件,減少了客戶端所需處理的對(duì)象數(shù)目,并使得子系統(tǒng)使用起來更加容易。通過引入外觀模式,客戶端代碼將變得很簡(jiǎn)單,與之關(guān)聯(lián)的對(duì)象也很少。
- 它實(shí)現(xiàn)了子系統(tǒng)與客戶端之間的松耦合關(guān)系,這使得子系統(tǒng)的變化不會(huì)影響到調(diào)用它的客戶端,只需要調(diào)整外觀類即可。
- 一個(gè)子系統(tǒng)的修改對(duì)其他子系統(tǒng)沒有任何影響,而且子系統(tǒng)內(nèi)部變化也不會(huì)影響到外觀對(duì)象。
外觀模式的主要缺點(diǎn)如下:
- 不能很好地限制客戶端直接使用子系統(tǒng)類,如果對(duì)客戶端訪問子系統(tǒng)類做太多的限制則減少了可變性和靈活性。
- 如果設(shè)計(jì)不當(dāng),增加新的子系統(tǒng)可能需要修改外觀類的源代碼,違背了開閉原則。
適用場(chǎng)景:
- 當(dāng)要為訪問一系列復(fù)雜的子系統(tǒng)提供一個(gè)簡(jiǎn)單入口時(shí)可以使用外觀模式。
- 客戶端程序與多個(gè)子系統(tǒng)之間存在很大的依賴性。引入外觀類可以將子系統(tǒng)與客戶端解耦,從而提高子系統(tǒng)的獨(dú)立性和可移植性。
- 在層次化結(jié)構(gòu)中,可以使用外觀模式定義系統(tǒng)中每一層的入口,層與層之間不直接產(chǎn)生聯(lián)系,而通過外觀類建立聯(lián)系,降低層之間的耦合度。
源碼分析外觀模式的典型應(yīng)用
spring jdbc中的外觀模式
查看 org.springframework.jdbc.support.JdbcUtils
public abstract class JdbcUtils {
public static void closeConnection(Connection con) {
if (con != null) {
try {
con.close();
}
catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
}
catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
}
public static Object getResultSetValue(ResultSet rs, int index, Class<?> requiredType) throws SQLException {
if (requiredType == null) {
return getResultSetValue(rs, index);
}
Object value = null;
boolean wasNullCheck = false;
// Explicitly extract typed value, as far as possible.
if (String.class.equals(requiredType)) {
value = rs.getString(index);
}
else if (boolean.class.equals(requiredType) || Boolean.class.equals(requiredType)) {
value = rs.getBoolean(index);
wasNullCheck = true;
}
else if (byte.class.equals(requiredType) || Byte.class.equals(requiredType)) {
value = rs.getByte(index);
wasNullCheck = true;
}
else if (short.class.equals(requiredType) || Short.class.equals(requiredType)) {
value = rs.getShort(index);
wasNullCheck = true;
}
else if (int.class.equals(requiredType) || Integer.class.equals(requiredType)) {
value = rs.getInt(index);
wasNullCheck = true;
}
else if (long.class.equals(requiredType) || Long.class.equals(requiredType)) {
value = rs.getLong(index);
wasNullCheck = true;
}
else if (float.class.equals(requiredType) || Float.class.equals(requiredType)) {
value = rs.getFloat(index);
wasNullCheck = true;
}
else if (double.class.equals(requiredType) || Double.class.equals(requiredType) ||
Number.class.equals(requiredType)) {
value = rs.getDouble(index);
wasNullCheck = true;
}
else if (byte[].class.equals(requiredType)) {
value = rs.getBytes(index);
}
else if (java.sql.Date.class.equals(requiredType)) {
value = rs.getDate(index);
}
else if (java.sql.Time.class.equals(requiredType)) {
value = rs.getTime(index);
}
else if (java.sql.Timestamp.class.equals(requiredType) || java.util.Date.class.equals(requiredType)) {
value = rs.getTimestamp(index);
}
else if (BigDecimal.class.equals(requiredType)) {
value = rs.getBigDecimal(index);
}
else if (Blob.class.equals(requiredType)) {
value = rs.getBlob(index);
}
else if (Clob.class.equals(requiredType)) {
value = rs.getClob(index);
}
else {
// Some unknown type desired -> rely on getObject.
value = getResultSetValue(rs, index);
}
if (wasNullCheck && value != null && rs.wasNull()) {
value = null;
}
return value;
}
// ...省略...
}
該工具類主要是對(duì)原生的 jdbc 進(jìn)行了封裝
Mybatis中的外觀模式
查看 org.apache.ibatis.session.Configuration 類中以 new 開頭的方法
public class Configuration {
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
// ...省略...
}
該類主要對(duì)一些創(chuàng)建對(duì)象的操作進(jìn)行封裝
Tomcat 中的外觀模式
Tomcat 源碼中大量使用了很多外觀模式

org.apache.catalina.connector.Request 和 org.apache.catalina.connector.RequestFacade 這兩個(gè)類都實(shí)現(xiàn)了 HttpServletRequest 接口
在 Request 中調(diào)用 getRequest() 實(shí)際獲取的是 RequestFacade 的對(duì)象
protected RequestFacade facade = null;
public HttpServletRequest getRequest() {
if (facade == null) {
facade = new RequestFacade(this);
}
return facade;
}
在 RequestFacade 中再對(duì)認(rèn)為是子系統(tǒng)的操作進(jìn)行封裝
public class RequestFacade implements HttpServletRequest {
/**
* The wrapped request.
*/
protected Request request = null;
@Override
public Object getAttribute(String name) {
if (request == null) {
throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
}
return request.getAttribute(name);
}
// ...省略...
}
SLF4J 中的外觀模式
SLF4J 是簡(jiǎn)單的日志外觀模式框架,抽象了各種日志框架例如 Logback、Log4j、Commons-logging 和 JDK 自帶的 logging 實(shí)現(xiàn)接口。它使得用戶可以在部署時(shí)使用自己想要的日志框架。
SLF4J 沒有替代任何日志框架,它僅僅是標(biāo)準(zhǔn)日志框架的外觀模式。如果在類路徑下除了 SLF4J 再?zèng)]有任何日志框架,那么默認(rèn)狀態(tài)是在控制臺(tái)輸出日志。
日志處理框架 Logback 是 Log4j 的改進(jìn)版本,原生支持SLF4J(因?yàn)槭峭蛔髡唛_發(fā)的),因此 Logback+SLF4J 的組合是日志框架的最佳選擇,比 SLF4J+其它日志框架 的組合要快一些。而且Logback的配置可以是XML或Groovy代碼。
SLF4J 的 helloworld 如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
下圖為 SLF4J 與日志處理框架的綁定調(diào)用關(guān)系

應(yīng)用層調(diào)用 slf4j-api.jar,slf4j-api.jar 再根據(jù)所綁定的日志處理框架調(diào)用不同的 jar 包進(jìn)行處理
參考:
劉偉:設(shè)計(jì)模式Java版
慕課網(wǎng)java設(shè)計(jì)模式精講 Debug 方式+內(nèi)存分析
Java日志框架:slf4j作用及其實(shí)現(xiàn)原理
后記
歡迎評(píng)論、轉(zhuǎn)發(fā)、分享,您的支持是我最大的動(dòng)力
更多內(nèi)容可訪問我的個(gè)人博客:http://laijianfeng.org
關(guān)注【小旋鋒】微信公眾號(hào),及時(shí)接收博文推送
