设计模式 | 外观模式及典型应用

前言

本文的主要内容:java

  • 介绍外观模式spring

  • 示例sql

    • 本身泡茶apache

    • 到茶馆喝茶设计模式

  • 外观模式总结api

  • 外观模式的典型应用微信

    • spring JDBC 中的外观模式session

    • Mybatis中的外观模式app

    • Tomcat 中的外观模式框架

    • SLF4J 中的外观模式

外观模式

外观模式是一种使用频率很是高的结构型设计模式,它经过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,下降子系统与客户端的耦合度,且客户端调用很是方便。

外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,经过引入一个新的外观角色能够下降原有系统的复杂度,同时下降客户类与子系统的耦合度。

外观模式包含以下两个角色:

Facade(外观角色):在客户端能够调用它的方法,在外观角色中能够知道相关的(一个或者多个)子系统的功能和责任;在正常状况下,它将全部从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。

SubSystem(子系统角色):在软件系统中能够有一个或者多个子系统角色,每个子系统能够不是一个单独的类,而是一个类的集合,它实现子系统的功能;每个子系统均可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另一个客户端而已。

外观模式的目的不是给予子系统添加新的功能接口,而是为了让外部减小与子系统内多个模块的交互,松散耦合,从而让外部可以更简单地使用子系统。

外观模式的本质是:封装交互,简化调用

示例

泡茶须要水 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;
    // 省略...
}    

烧水须要用水壶烧,将水加热

public class KettleService {
    public void waterBurning(String who, Water water, int burnTime{
        // 烧水,计算最终温度
        int finalTermperature = Math.min(100, water.getTemperature() + burnTime * 20);
        water.setTemperature(finalTermperature);
        System.out.println(who + " 使用水壶烧水,最终水温为 " + finalTermperature);
    }
}

泡茶,将烧好的水与茶叶进行冲泡,最终获得一杯茶水

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());
    }
}

本身泡茶喝

张3、李四各自泡茶喝,各自都须要准备茶具、茶叶、水,各自还要完成烧水、泡茶等操做

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(1015);
        TeaLeaf teaLeaf2 = new TeaLeaf("碧螺春");
        kettleService2.waterBurning(lisi.getName(), water2, 4);
        Teawater teawater2 = teasetService2.makeTeaWater(lisi.getName(), water2, teaLeaf2);
        lisi.drink(teawater2);
    }
}

输出为

张三 使用水壶烧水,最终水温为 80
张三 泡了一杯容量为 10, 温度为 80 的西湖龙井茶水
张三 喝了一杯容量为 10, 温度为 80 的西湖龙井茶水

李四 使用水壶烧水,最终水温为 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(1015);
                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;
        }
    }
}

张三和李四点茶,只须要告诉茶馆套餐编号便可,水、茶叶由茶馆准备,烧水泡茶的操做由茶馆统一完成

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);
    }
}

输出为

老舍茶馆 使用水壶烧水,最终水温为 80
老舍茶馆 泡了一杯容量为 10, 温度为 80 的西湖龙井茶水
张三 喝了一杯容量为 10, 温度为 80 的西湖龙井茶水

老舍茶馆 使用水壶烧水,最终水温为 90
老舍茶馆 泡了一杯容量为 15, 温度为 90 的碧螺春茶水
李四 喝了一杯容量为 15, 温度为 90 的碧螺春茶水

到茶馆喝茶模式图

到茶馆喝茶模式图

外观模式总结

外观模式的主要优势以下:

  • 它对客户端屏蔽了子系统组件,减小了客户端所需处理的对象数目,并使得子系统使用起来更加容易。经过引入外观模式,客户端代码将变得很简单,与之关联的对象也不多。

  • 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只须要调整外观类便可。

  • 一个子系统的修改对其余子系统没有任何影响,并且子系统内部变化也不会影响到外观对象。

外观模式的主要缺点以下:

  • 不能很好地限制客户端直接使用子系统类,若是对客户端访问子系统类作太多的限制则减小了可变性和灵活性。

  • 若是设计不当,增长新的子系统可能须要修改外观类的源代码,违背了开闭原则。

适用场景:

  • 当要为访问一系列复杂的子系统提供一个简单入口时能够使用外观模式。

  • 客户端程序与多个子系统之间存在很大的依赖性。引入外观类能够将子系统与客户端解耦,从而提升子系统的独立性和可移植性。

  • 在层次化结构中,能够使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而经过外观类创建联系,下降层之间的耦合度。

源码分析外观模式的典型应用

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;
    }
    // ...省略...
}    

该工具类主要是对原生的 jdbc 进行了封装

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;
    }
    // ...省略...
}

该类主要对一些建立对象的操做进行封装

Tomcat 中的外观模式

Tomcat 源码中大量使用了外观模式

Tomcat中的外观模式

org.apache.catalina.connector.Requestorg.apache.catalina.connector.RequestFacade 这两个类都实现了 HttpServletRequest 接口

Request 中调用 getRequest() 实际获取的是 RequestFacade 的对象

protected RequestFacade facade = null;

public HttpServletRequest getRequest({
    if (facade == null) {
        facade = new RequestFacade(this);
    }
    return facade;
}

RequestFacade 中再对认为是子系统的操做进行封装

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 是简单的日志外观模式框架,抽象了各类日志框架例如 LogbackLog4jCommons-loggingJDK 自带的 logging 实现接口。它使得用户能够在部署时使用本身想要的日志框架。

SLF4J 没有替代任何日志框架,它仅仅是标准日志框架的外观模式。若是在类路径下除了 SLF4J 再没有任何日志框架,那么默认状态是在控制台输出日志。

日志处理框架 Logback 是 Log4j 的改进版本,原生支持SLF4J(由于是同一做者开发的),所以 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 与日志处理框架的绑定调用关系

SLF4J与日志处理框架的绑定调用关系

应用层调用 slf4j-api.jarslf4j-api.jar 再根据所绑定的日志处理框架调用不一样的 jar 包进行处理

参考:  
刘伟:设计模式Java版  
慕课网java设计模式精讲 Debug 方式+内存分析    
Java日志框架:slf4j做用及其实现原理

推荐阅读

设计模式 | 简单工厂模式及典型应用  
设计模式 | 工厂方法模式及典型应用    
设计模式 | 抽象工厂模式及典型应用    
设计模式 | 建造者模式及典型应用  
设计模式 | 原型模式及典型应用

关注【小旋锋】微信公众号

本文分享自微信公众号 - 小旋锋(whirlysBigData)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索