1.外观模式概念
1.1定义
外观模式隐藏系统的复杂性,为子系统中的一组接口提供一个一致的界面。这种类型的设计模式属于结构型模式,它向现有的系统提供一个高层次的接口, 使得子系统更易于使用。
简单的来说就是对外提供一个简单接口,隐藏实现的逻辑。比如常用电脑的电源键,我们只需按电源键,就可以让它启动或者关闭,无需知道它是怎么启动的(启动CPU、启动内存、启动硬盘),怎么关闭的(关闭硬盘、关闭内存、关闭CPU);
外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需调用这个接口,而无需关心这个子系统的内部细节。
外观模式是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。
外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度。
外观模式的本质是:封装交互,简化调用。
1.2角色分配
外观模式类图
- Facade(外观角色):统一的高层接口,通过该角色将客户端的请求委托到不同的子系统。
- SubSystem(子系统角色):相当于零件,多个零件组合完成一个功能。每个功能就是一个子系统。
1.3场景分析
这是我们平时开发用的最多的模式之一,可以说我们每天都在用这个模式在写代码,只是我们可能没有意识到。
根据 “单一职责原则” ,在软件中将一个系统划分为若干个子系统有利于降低系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。
外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。 外观模式的目的在于降低系统的复杂程度。 外观模式从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。
- 比如通过在线商城买东西:判断是否有货-创建订单-支付扣款-物流创建-通知消息-用户积分新增。我们的一个下单操作可能会涉及很多系统之间的调用,而对外我们只是通过一个下单接口。
- 去茶馆喝茶跟自己泡茶喝:去茶馆直接说像喝茶就什么茶都有了,还有美女倒茶。而自己泡茶要先烧水、洗茶具、温茶具、倒茶、洗茶具。
- 比如电脑开机与关机。通过一个按钮就可以启动 CPU、内存、磁盘…用户不必跟每个硬件打交道
2.代码实现
模拟开关机通过外观模式。
我们的 CPU 、Disk、Memory 其实就是我们不同的 Service ,所以我们先定义出各个子系统的业务功能。
子系统角色
public class CPU {
public void startup(){
System.out.println("cpu startup!");
}
public void shutdown(){
System.out.println("cpu shutdown!");
}
}
public class Disk {
public void startup(){
System.out.println("disk startup!");
}
public void shutdown(){
System.out.println("disk shutdown!");
}
}
public class Memory {
public void startup(){
System.out.println("memory startup!");
}
public void shutdown(){
System.out.println("memory shutdown!");
}
}
外观角色
/**
* 外观类:给客户端提供了一个视图
* Created by unique on 2017/5/14.
*/
public class ComputerFacade {
private CPU cpu = new CPU();
private Memory memory = new Memory();
private Disk disk = new Disk();
private ComputerFacade() {
}
// 单例模式
private static class Holder {
private static ComputerFacade instance = new ComputerFacade();
}
public static ComputerFacade getInstance() {
return Holder.instance;
}
/**
* 开机一步到位,用户不必根各个硬件打交道,就像一个开关搞定
*/
public void startup(){
System.out.println("start the computer!");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("start computer finished!");
}
public void shutdown(){
System.out.println("begin to close the computer!");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("computer closed!");
}
}
客户角色
public class UserClient {
public static void main(String[] args) {
ComputerFacade facade = ComputerFacade.getInstance();
facade.startup();
System.out.println("时间到了,可以关机了.....");
facade.shutdown();
}
}
3.外观模式优缺点及适用场景
外观模式的主要优点如下:
- 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
- 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
- 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
外观模式的主要缺点如下:
- 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。
- 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。
适用场景:
- 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。
- 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。
- 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。
4.源码分析外观模式的典型应用
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;
}
// ...省略...
}
该类主要对一些创建对象的操作进行封装
5.与其他模式关系
-
适配器模式意图是将接口转换成不同的接口,外观模式的意图简化接口。
-
当只需对客户端代码隐藏子系统创建对象的方式时,可以使用抽象工厂模式来代替。
-
享元模式展示了如何生成大量的小型对象,外观模式则展示了如何用一个对象来代表整个子系统。
-
外观模式和中介者模式的职责类似:它们都尝试在大量紧密耦合的类中组织起合作。
外观为子系统中的所有对象定义了一个简单接口,但是它不提供任何新功能。子系统本身不会意识到外观的存在。子系统中的对象可以直接进行交流。
中介者将系统中组件的沟通行为中心化。各组件只知道中介者对象,无法直接相互交流。 -
外观类通常可以转换为单例类,因为在大部分情况下一个外观对象就足够了。
-
外观模式与代理模式的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。代理与其服务对象遵循同一接口,使得自己和服务对象可以互换,在这一点上它与外观不同。