设计模式之——门面模式

门面模式

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

原文: :Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.

解释:要求一个子系统的外部与其内部的通信必须通过一个同一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。

其实,在我们日常的编码工作中,我们都在有意无意地大量使用门面模式,但凡只要高层模块需要调度多个子系统(2 个以上类对象),我们都会自觉地创建一个新类封装这些子系统,提供精简接口,让高层模块可以更加容易间接调用这些子系统的功能。尤其是现阶段各种第三方 SDK,各种开源类库,很大概率都会使用门面模式。尤其是你觉得调用越方便的,门面模式使用的一般更多。

门面模式的应用场景

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

门面模式的通用写法

门面模式的 UML 类图:
image.png

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

门面模式的通用代码,首先分别创建 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() {
        this.a.doA();
    }
    // 对外接口
    public void doB() {
        this.b.doB();
    }
    // 对外接口
    public void doC() {
        this.c.doC();
    }
}

来看客户端代码:

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

门面模式业务场景实例

xx社区上线了一个积分兑换礼品的商城,这礼品商城中的大部分功能并不是全部重新开发的,而是要去对接已有的各个子系统

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

首先,创建礼品的实体类 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;
    }
}

然后创建外观角色 GiftFacdeService 类,对外只开放一个兑换礼物的 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 Boot 编程思想》");
    GiftFacadeService giftFacadeService = new GiftFacadeService();
    giftFacadeService.exchange(giftInfo);
}

门面模式在源码中的应用

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

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

再来看一个 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 中关键组件操作的封装。

另外地在 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 类。

门面模式的优缺点

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356

推荐阅读更多精彩内容