前言

本文的主要内容:

  • 介绍外观模式
  • 示例
    • 自己泡茶
    • 到茶馆喝茶
  • 外观模式总结
  • 外观模式的典型应用
    • spring JDBC 中的外观模式
    • Mybatis中的外观模式
    • 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());}
}

自己泡茶喝

张三、李四各自泡茶喝,各自都需要准备茶具、茶叶、水,各自还要完成烧水、泡茶等操作

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

输出为

张三 使用水壶烧水,最终水温为 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(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;}}
}

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

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 源码中大量使用了很多外观模式

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;@Overridepublic 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-api.jarslf4j-api.jar 再根据所绑定的日志处理框架调用不同的 jar 包进行处理

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

推荐阅读

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

更多内容可访问我的个人博客:http://laijianfeng.org

设计模式 | 外观模式及典型应用相关推荐

  1. 设计模式 | 备忘录模式及典型应用

    本文的主要内容: 介绍备忘录模式 示例 备忘录模式总结 备忘录模式 备忘录模式经常可以遇到,譬如下面这些场景: 浏览器回退:浏览器一般有浏览记录,当我们在一个网页上点击几次链接之后,可在左上角点击左箭 ...

  2. 设计模式 | 策略模式及典型应用

    本文的主要内容: 介绍策略模式 示例 商场购物打折策略的实现 策略模式总结 源码分析策略模式的典型应用 Java Comparator 中的策略模式 Spring Resource 中的策略模式 Sp ...

  3. 设计模式 | 组合模式及典型应用

    本文的主要内容: 介绍组合模式 示例 组合模式总结 源码分析组合模式的典型应用 java.awt中的组合模式 Java集合中的组合模式 Mybatis SqlNode中的组合模式 组合模式 树形结构不 ...

  4. Python设计模式-外观模式

    Python设计模式-外观模式 代码基于3.5.2,代码如下; #coding:utf-8 # 外观模式class AlarmSensor:def run(self):print("Alar ...

  5. 设计模式--------外观模式

    设计模式--------外观模式 介绍外观模式的四大问题 现在的写法有什么问题吗? 为什么要用外观模式? 什么是外观模式? 外观模式有什么好处? 使用一个例子进行问题的说明 假如要制作一个智能系统,要 ...

  6. 设计模式 | 解释器模式及典型应用

    微信原文:设计模式 | 解释器模式及典型应用 博客原文:设计模式 | 解释器模式及典型应用 本文主要介绍解释器模式,在日常开发中,解释器模式的使用频率比较低 解释器模式 解释器模式(Interpret ...

  7. 2.进阶--研磨设计模式----外观模式

    1.外观模式: 作用或者优点: 为某个系统的组合功能提供一个外层,也就是客户端使用系统的某个组合功能(需要与多个子模块交互)时候,可以通过提供一个外层,来避免客户端对子模块复杂的调用,简化了客户端的使 ...

  8. 设计模式 | 外观模式

    1 | 外观模式的概述 在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类需要和多个业务类交互,而这些需要交互的业务类经常会作为一个整体出现,由于涉及的类比较多,导致使用时代码比较复杂,此时特 ...

  9. [设计模式]外观模式

    1. 定义 外观模式 提供了一个统一的接口,用来访问子系统中的一群接口.外观定义了一个高层接口,让子系统更加容易使用. 外观不仅简化了系统内接口的使用,也将客户从组件中的子系统中解耦,外观和适配器都可 ...

最新文章

  1. win7查看电脑上openCV的版本
  2. 北大教授:学术会议与学术研讨渐行渐远,已沦为学术表演~
  3. eclipse中动态web项目快捷方式设置
  4. mysql锁表查询_Mysql数据库锁情况下开启备份导致数据库无法访问处理分享
  5. python多版本共存问题
  6. 全民讨伐 Google AI “作恶”项目
  7. python list相关知识
  8. ubuntu怎么将Dash切换位bash
  9. mysql php教程笔记_Mysql学习笔记(一)
  10. 实验吧-杂项-Only one file(多文件合并、firework多图层图片)
  11. vue 动态引入组件
  12. 米家推出新款石头机器人,扫拖一体,指哪去哪!
  13. 爆料!爆料!亚马逊跨境电商店铺骗局被骗真相曝光!不要在上当了!
  14. Unsafe code may only appear if compiling with /unsafe
  15. nginx设置域名跳转另一个域名
  16. 五轴转台哪家质量比较好?
  17. DIY超好吃的橙子果酱
  18. main函数的argc与arg
  19. 课堂教学评价的主要内容
  20. MPG(MPEG2 Program Stream)格式解析

热门文章

  1. android 读写文件 简书,Android 中的文件操作
  2. 十年磨一剑,剑指IT技术之巅,WOT 全球技术创新大会 2022盛大开启
  3. C++自制游戏《Fighter》
  4. 解决Visual Studio2019登录微软账户登录不上的问题
  5. eclipse birt使用脚本实现换行
  6. 周期性行业是什么意思_聊聊周期性行业 1.什么是周期性行业 周期性行业,就是指受经济周期影响较大的行业,经济低迷,行业亦表现为低迷;经济繁荣,行业也会表现得高... - 雪球...
  7. 字节笔试题(含答案)
  8. 【汇正财经】什么是市盈率?
  9. 蚂蚁金服首席架构师何昌华:开源 SQLFlow 是牛刀初试,实时大数据系统才是未来基石...
  10. Linux常用的基本命令