结构型。
应用场景明确,主要在接口设计方面,以兼顾接口的易用性、通用性。
为保证接口可复用性(或通用性),需将接口设计尽量地细粒度&&职责单一:
本文就教你如何解决接口可复用性和易用性矛盾。
Facade Design Pattern,也叫外观模式,在GoF的《设计模式》中定义:
Provide a unified interface to a set of interfaces in a subsystem. Facade Pattern defines a higher-level interface that makes the subsystem easier to use.
门面模式为子系统提供一组统一接口,定义一组高层接口让子系统更易用。
不同应用场景下,使用门面模式的意图也不同。
“门面模式让子系统更加易用”,门面模式定义中的“子系统(subsystem)”也有多种理解方式。既能是个完整系统,也能是更细粒度的类或模块。
封装系统的底层实现,隐藏系统复杂性,提供一组更简单易用、更高层接口。如:
从隐藏实现复杂性,提供更易用接口的意图,门面模式类似迪米特法则(最少知识原则)和接口隔离原则:两个有交互的系统,只暴露有限的必要接口。
门面模式还类似封装、抽象设计思想,提供更抽象的接口,封装底层实现细节。
如系统A,提供a、b、c、d接口。系统B完成某功能,需调用A系统a、b、d接口。利用门面模式,提供一个包裹a、b、d接口调用的门面接口x,给系统B直接使用。
让B直接调用a、b、d也没啥啊,为啥还提供个包裹a、b、d的接口x?
假设A是服务器,B是客户端。客户端通过服务器提供的接口获取数据。为提高客户端响应速度,要尽量减少客户端与服务器之间的网络通信次数。
若某业务功能(如显示某页面信息)需“依次”调用a、b、d三接口,因业务特点,不支持并发调用这三接口。现在发现客户端响应较慢,排查发现,因过多接口调用导致过多的网络通信。利用门面模式,让服务器提供一个包裹a、b、d三个接口调用的接口x。客户端调用一次接口x,就获取到所有想要数据,将网络通信次数从3次减少到1次,提高客户端响应速度。
这就是门面模式的一个用途,即解决性能问题。
将多个接口调用替换为一个门面接口调用,减少网络通信成本,提高App客户端的响应速度。
从代码实现角度
若门面接口不多,完全可将其和非门面接口放一块,也无需特殊标记,当作普通接口用即可。
若门面接口很多,可基于已有接口,再抽象一层,专门放置门面接口,从类、包的命名上跟原来的接口层做区分。
若门面接口特多,且很多都是跨多个子系统,可将门面接口放到一个新子系统。
有用户、钱包两个业务域,都对外暴露一系列接口,如用户的crud、钱包的crud。用户注册时,不仅创建用户(User表),还给用户创建一个钱包(Wallet表)。
可依次调用用户的创建接口、钱包的创建接口。但用户注册需支持事务,即创建用户和钱包的两个操作,都成功/失败。
要支持两个接口调用在一个事务执行,很难实现,涉及分布式事务。分布式事务框架或补偿机制复杂度都较高。
最简单的,利用DB事务或Spring事务,在一个事务中,执行创建用户、创建钱包两个SQL操作。就要求两个SQL操作要在一个接口完成,使用门面模式,即再设计一个包裹这两个操作的新接口,让新接口在一个事务中执行两个SQL操作。
可若再抽离一个门面模式的子模块,里面肯定要包含用户/钱包数据源。然后用户、钱包系统又是独立子模块,这样一个数据源散到很多地方? 若用户表、钱包表要拆离成两套库就GG。没错,但若用门面包装一个接口,使用分布式事务框架解决事务问题,其他多个业务就只用调用这个接口,无需自己实现分布式事务问题。相当于复用解决事务问题的代码。
这种门面接口在多租户系统常见,如初始化一个租户分很多步:
有时,为易用性和交易一致性,就是要把这些步骤封装在一个接口里(其实每一步都有一个接口)。
类、模块、系统之间的“通信”,一般都是通过接口调用。接口设计的好坏,直接影响到类、模块、系统是否好用。完成接口设计,就相当于完成了一半的开发任务。只要接口设计得好,那代码就不会太差。
接口粒度设计得太大,太小都不好:
接口可复用性和易用性需要权衡,基本处理原则:尽量保持接口的可复用性,但针对特殊情况,允许提供冗余的门面接口,来提供更易用的接口。