意外发现,原来你不知道自己每天都在用门面模式
本文节选自《设计模式就该这样学》
1 使用门面模式整合已知API的功能
一般的电商平台都是整合众多的子系统聚合到一起形成一个大型的购物平台,一般情况下,有很多现成的功能都不是重新开发的,而是要去对接已有的各个子系统,这些子系统可能涉及积分系统、支付系统、物流系统的接口调用。如果所有的接口调用全部由前端发送网络请求去调用现有接口,一则会增加前端开发人员的难度,二则会增加一些网络请求,影响页面性能。此时就可以发挥门面模式的优势了。将所有现成的接口全部整合到一个类中,由后端提供统一的接口供前端调用,这样前端开发人员就不需要关心各接口的业务关系,只需要把精力集中在页面交互上。我们用代码来模拟一个积分兑换礼品的业务场景。
首先创建礼品的实体类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 pointsGift){
//扣减积分
System.out.println("支付" + pointsGift.getName() + " 积分成功");
return true;
}
}
创建物流系统ShippingService类。
public class ShippingService {
//发货
public String delivery(GiftInfo giftInfo){
//物流系统的对接逻辑
System.out.println(giftInfo.getName() + "进入物流系统");
String shippingOrderNo = "666";
return shippingOrderNo;
}
}
接着创建外观角色GiftFacadeService类,对外只开放一个兑换礼物的exchange()方法,在exchange()方法内部整合3个子系统的所有功能。
public class GiftFacadeService {
private QualifyService qualifyService = new QualifyService();
private PaymentService pointsPaymentService = new PaymentService();
private ShippingService shippingService = new ShippingService();
//兑换
public void exchange(GiftInfo giftInfo){
if(qualifyService.isAvailable(giftInfo)){
//资格校验通过
if(pointsPaymentService.pay(giftInfo)){
//如果支付积分成功
String shippingOrderNo = shippingService.delivery(giftInfo);
System.out.println("物流系统下单成功,订单号是:"+shippingOrderNo);
}
}
}
}
最后来看客户端代码。
public static void main(String[] args) {
GiftInfo giftInfo = new GiftInfo("《Spring 5核心原理》");
GiftFacadeService giftFacadeService = new GiftFacadeService();
giftFacadeService.exchange(giftInfo);
}
运行结果如下图所示。
通过这样一个案例对比,相信大家对门面模式的印象就非常深刻了。
2 门面模式在Spring源码中的应用
先来看Spring JDBC模块下的JdbcUtils类,它封装了与JDBC相关的所有操作,代码片段如下。
public abstract class JdbcUtils {
public static final int TYPE_UNKNOWN = -2147483648;
private static final Log logger = LogFactory.getLog(JdbcUtils.class);
public JdbcUtils() {
}
public static void closeConnection(Connection con) {
if(con != null) {
try {
con.close();
} catch (SQLException var2) {
logger.debug("Could not close JDBC Connection", var2);
} catch (Throwable var3) {
logger.debug("Unexpected exception on closing JDBC Connection", var3);
}
}
}
public static void closeStatement(Statement stmt) {
if(stmt != null) {
try {
stmt.close();
} catch (SQLException var2) {
logger.trace("Could not close JDBC Statement", var2);
} catch (Throwable var3) {
logger.trace("Unexpected exception on closing JDBC Statement", var3);
}
}
}
public static void closeResultSet(ResultSet rs) {
if(rs != null) {
try {
rs.close();
} catch (SQLException var2) {
logger.trace("Could not close JDBC ResultSet", var2);
} catch (Throwable var3) {
logger.trace("Unexpected exception on closing JDBC ResultSet", var3);
}
}
}
...
}
更多其他操作,看它的结构就非常清楚了,如下图所示。
3 门面模式在MyBatis源码中的应用
再来看一个MyBatis中的Configuration类,其中有很多new开头的方法,源码如下。
public MetaObject newMetaObject(Object object) {
return MetaObject.forObject(object, this.objectFactory, this.objectWrapperFactory, this.reflectorFactory);
}
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
BoundSql boundSql) {
ParameterHandler parameterHandler =
mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
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 = (ResultSetHandler)this.interceptorChain.pluginAll (resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler =
new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll (statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return this.newExecutor(transaction, this.defaultExecutorType);
}
上面这些方法都是对JDBC中关键组件操作的封装。
4 门面模式在Tomcat源码中的应用
另外,门面模式在Tomcat的源码中也有体现,也非常有意思。以RequestFacade类为例,来看其源码。
public class RequestFacade implements HttpServletRequest {
...
@Override
public String getContentType() {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
return request.getContentType();
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
return request.getInputStream();
}
@Override
public String getParameter(String name) {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
if (Globals.IS_SECURITY_ENABLED){
return AccessController.doPrivileged(
new GetParameterPrivilegedAction(name));
} else {
return request.getParameter(name);
}
}
...
}
从名字就知道它用了门面模式。它封装了非常多的request操作,也整合了很多servlet-api以外的内容,给用户使用提供了很大便捷。同样,Tomcat针对Response和Session也封装了对应的ResponseFacade类和StandardSessionFacade类,感兴趣的小伙伴可以深入了解一下。
小伙伴们是不是意外地发现,你每天都在用门面模式?
关注微信公众号『 Tom弹架构 』回复“设计模式”可获取完整源码。
本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom弹架构 』可获取更多技术干货!