意外發現,原來你不知道自己每天都在用門面模式


本文節選自《設計模式就該這樣學》

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

運行結果如下圖所示。

file

通過這樣一個案例對比,相信大家對門面模式的印象就非常深刻了。

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

    }
    ...
}

更多其他操作,看它的結構就非常清楚了,如下圖所示。

file

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彈架構:30個設計模式真實案例(附源碼),挑戰年薪60W不是夢

本文為“Tom彈架構”原創,轉載請注明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力。關注微信公眾號『 Tom彈架構 』可獲取更多技術干貨!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM