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

Tom彈架構發表於2021-11-13

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

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彈架構 』可獲取更多技術乾貨!

相關文章