# 外观模式

定义:又叫门面模式,提供了一个统一的接口,用来访问子系统中的一群接口

特点:定义了一个高层接口,让子系统更容易使用

类型:结构型

# 适用场景

  • 子系统越来越复杂,增加外观模式提供简单调用接口
  • 构建多层系统接口,利用外观对象作为每层的入口,简化层间调用

# 优点

  • 简化了调用过程,无需了解深入子系统,防止带来风险
  • 减少系统以来、松散耦合
  • 更好的划分访问层次
  • 符合迪米特法则,即最少知道原则

# 缺点

  • 增加子系统、扩展子系统行为容易引起风险
  • 不符合开闭原则

# 相关设计模式

  • 中介则模式

    • 中介则模式:关注的是子系统内部之间的交互
    • 外观模式:关注的是外界和子系统的交互
  • 单例模式:通常可以吧外观对象做成单例使用

  • 抽象工程:外观类可以通过工厂类获取子系统的实例

# 代码

假设一个场景:慕课网的积分兑换,那么可能的子系统有:

  1. 库存校验系统
  2. 积分支付系统
  3. 物流系统
/**
 * 积分礼物
 *
 * @author : zhuqiang
 * @date : 2018/12/24 9:47
 */
public class PointsGift {
    private String name;

    public PointsGift(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "PointsGift{" +
                "name='" + name + '\'' +
                '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * 资格校验:积分校验,库存校验
 *
 * @author : zhuqiang
 * @date : 2018/12/24 9:49
 */
public class QualifyService {
    public boolean isAvailable(PointsGift pointsGift) {
        System.out.println("校验" + pointsGift + " 积分通过,库存通过");
        return true;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 积分支付服务
 *
 * @author : zhuqiang
 * @date : 2018/12/24 9:48
 */
public class PointsPaymentService {
    public boolean pay(PointsGift pointsGift) {
        System.out.println(pointsGift + " 支付扣减积分成功");
        return true;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 物流服务
 *
 * @author : zhuqiang
 * @date : 2018/12/24 9:52
 */
public class ShippingService {
    public String shipGift(PointsGift pointsGift) {
        // 物流系统对接逻辑
        System.out.println(pointsGift + " 进入物流系统");
        String shippingOrderNo = "6666";
        return shippingOrderNo;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

三个子系统服务,外部如果要使用,必须知道流程,这个时候就可以封装一个外观服务; 一个积分礼物兑换的服务暴露,这也符合迪米特法则,即最少知道原则;

/**
 * 外观模式服务
 *
 * @author : zhuqiang
 * @date : 2018/12/24 10:00
 */
public class GiftExchangeService {
    private QualifyService qualifyService;
    private PointsPaymentService pointsPaymentService;
    private ShippingService shippingService;

    public GiftExchangeService(QualifyService qualifyService, PointsPaymentService pointsPaymentService, ShippingService shippingService) {
        this.qualifyService = qualifyService;
        this.pointsPaymentService = pointsPaymentService;
        this.shippingService = shippingService;
    }

    /**
     * 礼物兑换
     */
    public void giftExchange(PointsGift pointsGift) {
        if (qualifyService.isAvailable(pointsGift)
                && pointsPaymentService.pay(pointsGift)) {
            String shippingOrderNo = shippingService.shipGift(pointsGift);
            System.out.println(pointsGift + " 返回的订单号 " + shippingOrderNo);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

可以看到外观服务组合了三个子系统(菱形)

测试代码

@Test
 public void giftExchange() {
     GiftExchangeService giftExchangeService = new GiftExchangeService(new QualifyService(), new PointsPaymentService(), new ShippingService());
     giftExchangeService.giftExchange(new PointsGift("iphone8"));
 }

=====================
校验PointsGift{name='iphone8'} 积分通过,库存通过
PointsGift{name='iphone8'} 支付扣减积分成功
PointsGift{name='iphone8'} 进入物流系统
PointsGift{name='iphone8'} 返回的订单号 6666
1
2
3
4
5
6
7
8
9
10
11

如果上图,客户端其实是不需要创建子系统的,在这里不是分布式的服务所以能看到客户端创建了子系统; 抛开这个因素,可以看到,客户端只和外观类交互了;

来模拟下上面所说的,让外观类去创建子系统服务,就如下图了,下图才是外观类使用的一个标准类图: 客户端只和外观类交互

对于不经常变化的来说,直接使用实体外观类,如果对于经常变化的,就需要使用抽象外观类了

# 外观模式源码解析

springjdbc+myabtis+tomcat

org.springframework.jdbc.support.JdbcUtils

public static void closeConnection(@Nullable 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);
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

该类的方法基本上都是对 java.sql 的外观封装,如上面关闭连接

org.apache.ibatis.session.Configuration

该类中 newXX 的方法就是外观方法

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}
1
2
3
4
5

# tomcat

源码下载 (opens new window) 强选择该页面下的 Source Code Distributions 部分中的 zip。解压之后导入 IDEA 查看。

org.apache.catalina.connector.Request org.apache.catalina.connector.RequestFacade

Request 是 javax.servlet.http.HttpServletRequest 的实现; RequestFacade 是对 Request 的一个外观类,在外部获取 getRequest 的时候,返回的就是这个外观类

/**
 * The facade associated with this request.
 */
protected RequestFacade facade = null;


/**
 * @return the <code>ServletRequest</code> for which this object
 * is the facade.  This method must be implemented by a subclass.
 */
public HttpServletRequest getRequest() {
    if (facade == null) {
        facade = new RequestFacade(this);
    }
    if (applicationRequest == null) {
        applicationRequest = facade;
    }
    return applicationRequest;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

比较下 外观类 和具体类的方法实现

org.apache.catalina.connector.Request#getAttribute
@Override
public Object getAttribute(String name) {
    // Special attributes
    SpecialAttributeAdapter adapter = specialAttributes.get(name);
    if (adapter != null) {
        return adapter.get(this, name);
    }

    Object attr = attributes.get(name);

    if (attr != null) {
        return attr;
    }

    attr = coyoteRequest.getAttribute(name);
    if (attr != null) {
        return attr;
    }
    ..... 省略很多代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
org.apache.catalina.connector.RequestFacade#getAttribute
@Override
public Object getAttribute(String name) {

    if (request == null) {
        throw new IllegalStateException(
                        sm.getString("requestFacade.nullRequest"));
    }

    return request.getAttribute(name);
}
1
2
3
4
5
6
7
8
9
10
11

在 TOMCAT 源码中大量的使用了外观模式