設(shè)計模式之——門面模式

門面模式

門面模式(Facade Pattern)又叫外觀模式,提供了一個統(tǒng)一的接口,用來訪問子系統(tǒng)中的一群接口。其主要特征是定義了一個高層接口,讓子系統(tǒng)更容易使用扇丛,屬于結(jié)構(gòu)性模式

原文: :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.

解釋:要求一個子系統(tǒng)的外部與其內(nèi)部的通信必須通過一個同一的對象進(jìn)行尉辑。門面模式提供一個高層次的接口帆精,使得子系統(tǒng)更易于使用。

其實(shí)隧魄,在我們?nèi)粘5木幋a工作中卓练,我們都在有意無意地大量使用門面模式,但凡只要高層模塊需要調(diào)度多個子系統(tǒng)(2 個以上類對象)购啄,我們都會自覺地創(chuàng)建一個新類封裝這些子系統(tǒng)襟企,提供精簡接口,讓高層模塊可以更加容易間接調(diào)用這些子系統(tǒng)的功能狮含。尤其是現(xiàn)階段各種第三方 SDK顽悼,各種開源類庫,很大概率都會使用門面模式几迄。尤其是你覺得調(diào)用越方便的蔚龙,門面模式使用的一般更多。

門面模式的應(yīng)用場景

1映胁、子系統(tǒng)越來越復(fù)雜木羹,增加門面模式提供簡單接口
2、構(gòu)建多層系統(tǒng)結(jié)構(gòu)屿愚,利用門面對象作為每層的入口汇跨,簡化層間調(diào)用务荆。

門面模式的通用寫法

門面模式的 UML 類圖:
image.png

門面模式主要包含 2 種角色:
外觀角色(Facade):也稱門面角色妆距,系統(tǒng)對外的統(tǒng)一接口穷遂;
子系統(tǒng)角色(SubSystem):可以同時有一個或多個 SubSystem。每個 SubSytem 都不是一個單獨(dú)的類娱据,而是一個類的集合蚪黑。SubSystem 并不知道 Facade 的存在,對于 SubSystem 而言中剩,Facade 只是另一個客戶端而已(即 FacadeSubSystem 透明)忌穿。

門面模式的通用代碼,首先分別創(chuàng)建 3 個子系統(tǒng)的業(yè)務(wù)邏輯 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");
    }
}

然后郊愧,創(chuàng)建外觀角色 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();
}

門面模式業(yè)務(wù)場景實(shí)例

xx社區(qū)上線了一個積分兌換禮品的商城朴译,這禮品商城中的大部分功能并不是全部重新開發(fā)的,而是要去對接已有的各個子系統(tǒng)

這些子系統(tǒng)可能涉及到積分系統(tǒng)属铁、支付系統(tǒng)眠寿、物流系統(tǒng)的接口調(diào)用。如果所有的接口調(diào)用全部由前端發(fā)送網(wǎng)絡(luò)請求去調(diào)用現(xiàn)有接口的話焦蘑,一則會增加前端開發(fā)人員的難度盯拱,二則會增加一些網(wǎng)絡(luò)請求影響頁面性能。這個時候就可以發(fā)揮門面模式的優(yōu)勢了例嘱。將所有現(xiàn)成的接口全部整合到一個類中狡逢,由后端提供統(tǒng)一的接口給前端調(diào)用,這樣前端開發(fā)人員就不需要關(guān)心各接口的業(yè)務(wù)關(guān)系拼卵,只需要把精力集中在頁面交互上甚侣。下面我們用代碼來模擬一下這個場景。

首先间学,創(chuàng)建禮品的實(shí)體類 GiftInfo:

public class GiftInfo {
    private String name;
    public GiftInfo(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

然后殷费,編寫各個子系統(tǒng)的業(yè)務(wù)邏輯代碼,分別創(chuàng)建積分系統(tǒng) QualifyService 類:

public class QualifyService {
    public boolean isAvailable(GiftInfo giftInfo) {
        System.out.println("校驗(yàn)" + giftInfo.getName() + " 積分資格通過,庫存通過");
        return true;
    }
}

支付系統(tǒng) PaymentService 類:

public class PaymentService {
    public boolean pay(GiftInfo pointsGift) {
        //扣減積分
        System.out.println("支付" + pointsGift.getName() + " 積分成功");
        return true;
    }
}

物流系統(tǒng)ShippingService 類:

public class ShippingService {
    //發(fā)貨
    public String delivery(GiftInfo giftInfo) {
        //物流系統(tǒng)的對接邏輯
        System.out.println(giftInfo.getName() + "進(jìn)入物流系統(tǒng)");
        String shippingOrderNo = "666";
        return shippingOrderNo;
    }
}

然后創(chuàng)建外觀角色 GiftFacdeService 類低葫,對外只開放一個兌換禮物的 exchange()方法详羡,在 exchange()方法內(nèi)部整合 3 個子系統(tǒng)的所有功能。

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)) {
            //資格校驗(yàn)通過
            if (pointsPaymentService.pay(giftInfo)) {
                //如果支付積分成功
                String shippingOrderNo = shippingService.delivery(giftInfo);
                System.out.println("物流系統(tǒng)下單成功,訂單號是:" + shippingOrderNo);
            }
        }
    }
}

最后嘿悬,來看客戶端代碼:

public static void main(String[] args) {
    GiftInfo giftInfo = new GiftInfo("《Spring Boot 編程思想》");
    GiftFacadeService giftFacadeService = new GiftFacadeService();
    giftFacadeService.exchange(giftInfo);
}

門面模式在源碼中的應(yīng)用

Spring JDBC 模塊下的 JdbcUtils 類实柠,它封裝了和JDBC 相關(guān)的所有操作,它一個代碼片段:

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

其他更多的操作善涨,看它的結(jié)構(gòu)就非常清楚了:
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 中關(guān)鍵組件操作的封裝。

另外地在 Tomcat 的源碼中也有體現(xiàn)蟹漓,也非常的有意思炕横。舉個例子 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 以外的一些內(nèi)容,給用戶使用提供了很大便捷嗽交。同樣卿嘲,Tomcat 對 Response 和 Session 當(dāng)也封裝了ResponseFacade 和 StandardSessionFacade 類。

門面模式的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):
1夫壁、簡化了調(diào)用過程拾枣,無需深入了解子系統(tǒng),以防給子系統(tǒng)帶來風(fēng)險盒让。
2梅肤、減少系統(tǒng)依賴、松散耦合
3糯彬、更好地劃分訪問層次凭语,提高了安全性
4、遵循迪米特法則撩扒,即最少知道原則似扔。

缺點(diǎn):
1、當(dāng)增加子系統(tǒng)和擴(kuò)展子系統(tǒng)行為時搓谆,可能容易帶來未知風(fēng)險
2炒辉、不符合開閉原則
3、某些情況下可能違背單一職責(zé)原則泉手。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末黔寇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子斩萌,更是在濱河造成了極大的恐慌缝裤,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颊郎,死亡現(xiàn)場離奇詭異憋飞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)姆吭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門榛做,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事检眯±謇蓿” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵锰瘸,是天一觀的道長刽严。 經(jīng)常有香客問我,道長获茬,這世上最難降的妖魔是什么港庄? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任倔既,我火速辦了婚禮恕曲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渤涌。我一直安慰自己佩谣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布实蓬。 她就那樣靜靜地躺著茸俭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪安皱。 梳的紋絲不亂的頭發(fā)上调鬓,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機(jī)與錄音酌伊,去河邊找鬼腾窝。 笑死,一個胖子當(dāng)著我的面吹牛居砖,可吹牛的內(nèi)容都是我干的虹脯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼奏候,長吁一口氣:“原來是場噩夢啊……” “哼循集!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蔗草,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤咒彤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后咒精,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镶柱,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年狠轻,在試婚紗的時候發(fā)現(xiàn)自己被綠了奸例。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖查吊,靈堂內(nèi)的尸體忽然破棺而出谐区,到底是詐尸還是另有隱情,我是刑警寧澤逻卖,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布宋列,位于F島的核電站,受9級特大地震影響评也,放射性物質(zhì)發(fā)生泄漏炼杖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一盗迟、第九天 我趴在偏房一處隱蔽的房頂上張望坤邪。 院中可真熱鬧,春花似錦罚缕、人聲如沸艇纺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽黔衡。三九已至,卻和暖如春腌乡,著一層夾襖步出監(jiān)牢的瞬間盟劫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工与纽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侣签,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓渣锦,卻偏偏與公主長得像硝岗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子袋毙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容