“这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战”
一、门面模式的定义
门面模式(Facade Pattern)又叫外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。其主要特征是定义了一个高层接口,让子系统更容易使用,属于结构性模式。
二、门面模式的应用场景
- 子系统越来越复杂,增加门面模式提供简单接口。
- 构建多层系统结构,利用门面对象作为每层的入口,简化层间调用。
三、门面模式的通用写法
门面模式主要包含两种角色:
- 外观角色(Facade):也称门面角色,系统对外的统一接口。
- 子系统角色(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);
}
}
运行结果如下:
通过这样一个案例对比之后,相信大家对门面模式的印象非常深刻了。
五、门面模式在源码中的应用
下面我们来看门面模式在源码中的应用,先来看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);
}
}
}
其他更多的操作,看它的结构就非常清楚了:
再看一个例子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类,感兴趣的小伙伴可以去深入了解一下。
六、门面模式的优缺点
优点:
- 简化了调用过程,无需深入了解系统,防止给子系统带来风险。
- 减少系统依赖,松散耦合。
- 更好的划分访问层次,提高了安全性。
- 遵循迪米特法则,即最少知道原则。
缺点:
- 当增加子系统和扩展子系统行为时,可能容易带来未知风险。
- 不符合开闭原则。
- 某些情况下可能违背单一职责原则。
七、友情链接
欢迎大家关注微信公众号(MarkZoe)互相学习、互相交流。