设计模式-门面模式学习之旅

570 阅读5分钟

“这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战

一、门面模式的定义

门面模式(Facade Pattern)又叫外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。其主要特征是定义了一个高层接口,让子系统更容易使用,属于结构性模式。

二、门面模式的应用场景

  1. 子系统越来越复杂,增加门面模式提供简单接口。
  2. 构建多层系统结构,利用门面对象作为每层的入口,简化层间调用。

三、门面模式的通用写法

门面模式主要包含两种角色:

  1. 外观角色(Facade):也称门面角色,系统对外的统一接口。
  2. 子系统角色(SubSystem):可以同时有一个或多个SubSystem。每个SubSystem都不是一个单独的类,而是一个类的集合。SubSystem并不知道Facade的存在,对于SubSystem而言,Facade只是另一个客户端而已(即Facade对SubSystem透明)。

下面是门面模式的通用代码,首先分别创建3个子系统的业务逻辑SubSystemA、SubSystemB、SubSystemC、代码很简单:

public class SubSystemA {
    public void doA(){
        System.out.println("doing A stuff");
    }
}
public class SubSystemB {
    public void doB() {
        System.out.println("doing B stuff");
    }
}
public class SubSystemC {
    public void doC() {
        System.out.println("doing C stuff");
    }
}

然后,创建外观角色Facade类:

public class Facade {
    private SubSystemA a = new SubSystemA();
    private SubSystemB b = new SubSystemB();
    private SubSystemC c = new SubSystemC();

    //对外接口
    public void doA() {
        a.doA();
    }

    //对外接口
    public void doB() {
        b.doB();
    }

    //对外接口
    public void doC() {
        c.doC();
    }
}

来看客户端代码:

public class Test {
    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.doA();
        facade.doB();
        facade.doC();
    }
}

四、门面模式在业务场景实例

比如某个社区上线了一个积分兑换礼品的商城,这礼品商城中的大部分功能并不是全部重新开发的,而是要去对接已有的各个子系统。这些子系统肯能涉及到积分系统、支付系统、物流系统的接口调用。如果所有的接口调用全部由前端发送网络请求去调用现有接口的话,一是会增加前端开发人员的难度,二是会增加一些网络请求影响页面性能。这个时候就可以发挥门面模式的优势了。将所有现成的接口全部整合到一个类中,由后端提供统一的接口给前端调用,这样前端开发人员就不需要关心各接口的业务关系,只需要把精力集中在页面交互上。下面我们用代码来模拟一下这个场景。

首先,创建礼品的实体类GiftInfo:

public class GiftInfo {
    private String name;

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

    public String getName() {
        return name;
    }
}

然后,编写各个子系统的业务逻辑代码,分别创建积分系统QualifyService类:

public class QualifyService {

    public boolean isAvailable(GiftInfo giftInfo) {
        System.out.println("校验" + giftInfo.getName() + "积分资格通过,库存通过");
        return true;
    }
}

支付系统PaymentService类:

public class PaymentService {

    public boolean pay(GiftInfo giftInfo) {
        //扣减积分
        System.out.println("支付" + giftInfo.getName() + "积分成功");
        return true;
    }
}

物流系统ShippingService类:

public class ShippingService {
    //发货
    public String delivery(GiftInfo giftInfo) {
        //物流系统的对接逻辑
        System.out.println(giftInfo.getName() + "进入物流系统");
        return "666";
    }
}

然后创建外观角色GiftFacadeService类,对外只开放一个兑换礼物的exchange()方法,在exchange()方法内部整合3个子系统的所有功能。

public class GiftFacadeService {
    private QualifyService qualifyService = new QualifyService();
    private PaymentService paymentService = new PaymentService();
    private ShippingService shippingService = new ShippingService();

    //兑换
    public void exchange(GiftInfo giftInfo) {
		//资格校验通过
        if (qualifyService.isAvailable(giftInfo)) {
            //如果支付积分成功
            if (paymentService.pay(giftInfo)) {
                String shippingOrderNo = shippingService.delivery(giftInfo);
                System.out.println("物流系统下单成功,订单号是:" + shippingOrderNo);
            }
        }
    }
}

最后来看客户端代码:

public class EgTest {

    public static void main(String[] args) {
        GiftInfo giftInfo = new GiftInfo("《DDD是什么鬼?》");
        GiftFacadeService facadeService = new GiftFacadeService();
        facadeService.exchange(giftInfo);
    }
}

运行结果如下:

image.png

通过这样一个案例对比之后,相信大家对门面模式的印象非常深刻了。

五、门面模式在源码中的应用

下面我们来看门面模式在源码中的应用,先来看Spring JDBC 模块下的JdbcUtils类,它封装了和JDBC相关的所有操作,类中的部分片段:

public abstract class JdbcUtils {

  public static final int TYPE_UNKNOWN = Integer.MIN_VALUE;

  private static final Log logger = LogFactory.getLog(JdbcUtils.class);

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

  public static void closeStatement(@Nullable Statement stmt) {
    if (stmt != null) {
      try {
        stmt.close();
      }
      catch (SQLException ex) {
        logger.trace("Could not close JDBC Statement", ex);
      }
      catch (Throwable ex) {
        // We don't trust the JDBC driver: It might throw RuntimeException or Error.
        logger.trace("Unexpected exception on closing JDBC Statement", ex);
      }
    }
  }

  public static void closeResultSet(@Nullable ResultSet rs) {
    if (rs != null) {
      try {
        rs.close();
      }
      catch (SQLException ex) {
        logger.trace("Could not close JDBC ResultSet", ex);
      }
      catch (Throwable ex) {
        // We don't trust the JDBC driver: It might throw RuntimeException or Error.
        logger.trace("Unexpected exception on closing JDBC ResultSet", ex);
      }
    }
  }

其他更多的操作,看它的结构就非常清楚了:

image.png

再看一个例子RequestFacade类,来看源码:

public class RequestFacade implements HttpServletRequest {
    protected Request request = null;
    protected static final StringManager sm = StringManager.getManager(RequestFacade.class);

    public RequestFacade(Request request) {
        this.request = request;
    }

    public void clear() {
        this.request = null;
    }

    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    public Object getAttribute(String name) {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return this.request.getAttribute(name);
        }
    }

    public Enumeration<String> getAttributeNames() {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return Globals.IS_SECURITY_ENABLED ? (Enumeration)AccessController.doPrivileged(new RequestFacade.GetAttributePrivilegedAction()) : this.request.getAttributeNames();
        }
    }

    public String getCharacterEncoding() {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return Globals.IS_SECURITY_ENABLED ? (String)AccessController.doPrivileged(new RequestFacade.GetCharacterEncodingPrivilegedAction()) : this.request.getCharacterEncoding();
        }
    }

    public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            this.request.setCharacterEncoding(env);
        }
    }

    public int getContentLength() {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return this.request.getContentLength();
        }
    }

    public String getContentType() {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return this.request.getContentType();
        }
    }

    public ServletInputStream getInputStream() throws IOException {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return this.request.getInputStream();
        }
    }

    public String getParameter(String name) {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return Globals.IS_SECURITY_ENABLED ? (String)AccessController.doPrivileged(new RequestFacade.GetParameterPrivilegedAction(name)) : this.request.getParameter(name);
        }
    }

我们看名字就知道它用了门面模式,它封装了非常多的request操作,也整合了很多servlet-api以外的一些内容,给用户使用提供了很大便捷。同样,Tomcat对Response和Session也封装了ResponseFacade和StandardSessionFacade类,感兴趣的小伙伴可以去深入了解一下。

六、门面模式的优缺点

优点:

  1. 简化了调用过程,无需深入了解系统,防止给子系统带来风险。
  2. 减少系统依赖,松散耦合。
  3. 更好的划分访问层次,提高了安全性。
  4. 遵循迪米特法则,即最少知道原则。

缺点:

  1. 当增加子系统和扩展子系统行为时,可能容易带来未知风险。
  2. 不符合开闭原则。
  3. 某些情况下可能违背单一职责原则。

七、友情链接

设计模式-工厂模式学习之旅

设计模式-单例模式学习之旅

设计模式-原型模式学习之旅

设计模式-建造者模式学习之旅

设计模式-代理模式学习之旅

欢迎大家关注微信公众号(MarkZoe)互相学习、互相交流。