設(shè)計(jì)模式 | 外觀模式及典型應(yīng)用

前言

本文的主要內(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 源碼中大量使用了很多外觀模式

Tomcat中的外觀模式

org.apache.catalina.connector.Requestorg.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-loggingJDK 自帶的 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)系

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í)接收博文推送

關(guān)注_小旋鋒_微信公眾號(hào)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容