2020-12-13——門面模式在MyBatis以及Tomcat原始碼中的應用

蒙奇D灬小武發表於2020-12-14

結構型模式                 ————順口溜:適裝橋組享代外

目錄

1、門面模式(外觀模式)

1.1 門面模式UML類圖

1.2 日常生活看門面模式

1.3 使用場景

1.4 Java程式碼實現

2、門面模式在原始碼中應用

2.1 MyBatis原始碼中門面模式體現

2.2 Tomcat原始碼中門面模式體現

3、門面模式優缺點及使用場景

3.1 優點

3.2 缺點

3.3 注意事項

3.4 使用場景


1、門面模式(外觀模式)

外觀模式(Facade Pattern)隱藏系統的複雜性,並向客戶端提供了一個客戶端可以訪問系統的介面。它向現有的系統新增一個介面,來隱藏系統的複雜性。這種模式涉及到一個單一的類,該類提供了客戶端請求的簡化方法和對現有系統類方法的委託呼叫。

  • 意圖:為子系統中的一組介面提供一個一致的介面,外觀模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。
  • 主要解決:降低訪問複雜系統的內部子系統時的複雜度,簡化客戶端與之的介面。
  • 何時使用: 1、客戶端不需要知道系統內部的複雜聯絡,整個系統只需提供一個"接待員"即可。 2、定義系統的入口。
  • 如何解決:客戶端不與系統耦合,外觀類與系統耦合。
  • 關鍵程式碼:在客戶端和複雜系統之間再加一層,這一層將呼叫順序、依賴關係等處理好。
  • 應用例項: 1、去醫院看病,可能要去掛號、門診、劃價、取藥,讓患者或患者家屬覺得很複雜,如果有提供接待人員,只讓接待人員來處理,就很方便。 2、JAVA 的三層開發模式。

1.1 門面模式UML類圖

這裡寫圖片描述

1.2 日常生活看門面模式

  1. 大話設計模式中的例子,散戶不直接購買股票,購買基金,交由基金經理來決定,基金經理就是一個門面模式。
  2. 我有錢了我想自己蓋一套小洋樓,門面模式如下圖:

這裡寫圖片描述

1.3 使用場景

  1. 為複雜的模組或子系統提供外界訪問的模組。
  2. 子系統相對獨立。
  3. 預防低水平人員帶來的風險。

1.4 Java程式碼實現

       使用門面模式還有一個附帶的好處,就是能夠有選擇性地暴露方法。一個模組中定義的方法可以分成兩部分,一部分是給子系統外部使用的,一部分是子系統內部模組之間相互呼叫時使用的。有了Facade類,那麼用於子系統內部模組之間相互呼叫的方法就不用暴露給子系統外部了。

比如,定義如下A、B、C模組。

public class Module {
    /**
     * 提供給子系統外部使用的方法
     */
    public void a1(){};
    
    /**
     * 子系統內部模組之間相互呼叫時使用的方法
     */
    public void a2(){};
    public void a3(){};
}

public class ModuleB {
    /**
     * 提供給子系統外部使用的方法
     */
    public void b1(){};
    
    /**
     * 子系統內部模組之間相互呼叫時使用的方法
     */
    public void b2(){};
    public void b3(){};
}

public class ModuleC {
    /**
     * 提供給子系統外部使用的方法
     */
    public void c1(){};
    
    /**
     * 子系統內部模組之間相互呼叫時使用的方法
     */
    public void c2(){};
    public void c3(){};
}
public class ModuleFacade {
    
    ModuleA a = new ModuleA();
    ModuleB b = new ModuleB();
    ModuleC c = new ModuleC();
    /**
     * 下面這些是A、B、C模組對子系統外部提供的方法
     */
    public void a1(){
        a.a1();
    }
    public void b1(){
        b.b1();
    }
    public void c1(){
        c.c1();
    }
}

這樣定義一個ModuleFacade類可以有效地遮蔽內部的細節,免得客戶端去呼叫Module類時,發現一些不需要它知道的方法。比如a2()和a3()方法就不需要讓客戶端知道,否則既暴露了內部的細節,又讓客戶端迷惑。對客戶端來說,他可能還要去思考a2()、a3()方法用來幹什麼呢?其實a2()和a3()方法是內部模組之間互動的,原本就不是對子系統外部的,所以乾脆就不要讓客戶端知道。

2、門面模式在原始碼中應用

2.1 MyBatis原始碼中門面模式體現

外觀模式,主要理解外觀。通俗一點可以認為這個模式是將子系統封裝到一起,提供給應用的層面就提供一個方法。不直接由應用層直接訪問子系統。

下面我們看看ibatis的原始碼來具體理解外觀模式。

public MetaObject newMetaObject(Object object) {
    return MetaObject.forObject(object, this.objectFactory, this.objectWrapperFactory, this.reflectorFactory);
}

上述程式碼其實是完成一個建立MetaObject的事情,但是它是將一個負責建立MetaObject的子系統放在了這個方法裡面。為什麼要這麼做?實際上如果直接讓我們應用層去使用MetaObject.forObject(object,this.objectFactory,this.objectWrapperFactory,this.reflectorFactory);這個方法。可以看出引數實在太多,而Configuration類使用外觀模式,外觀類並不具體實現什麼,他只是負責呼叫和管理子系統
下面看看configuration中的構造器

可以把上面的objectFactory,objectWrapperFactory,reflectorFactory看作三個子系統
接下來到MetaObject的裡面看看forObject方法

public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    return object == null ? SystemMetaObject.NULL_META_OBJECT : new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}

對應的建構函式

private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;
    if (object instanceof ObjectWrapper) {
        this.objectWrapper = (ObjectWrapper)object;
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
        this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
        this.objectWrapper = new MapWrapper(this, (Map)object);
    } else if (object instanceof Collection) {
        this.objectWrapper = new CollectionWrapper(this, (Collection)object);
    } else {
        this.objectWrapper = new BeanWrapper(this, object);
    }
}

可以看出這個MetaObject也是個將構造器私有的特殊單例模式,大致分析了一下就用下面的UML圖畫出

2.2 Tomcat原始碼中門面模式體現

tomcat中山使用了大量的外觀模式,因為Tomcat中有很多不同元件,每個元件要相互通訊,但是又不能將自己內部資料過多的暴露給其他元件。用門面模式隔離資料是個很好的方法。

下面是Request上使用的門面模式:

使用過Servlet的人都清楚,除了要在web.xml做相應的配置外,還需繼承一個叫HttpServlet的抽象類,並且重寫doGet與doPost方法(當然只重寫service方法也是可以的)。

public class TestServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doPost(request, response);
            
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }
}

可以看出doGet與doPost方法有兩個引數,引數型別是介面HttpServletRequest與介面HttpServletResponse,那麼從Tomcat中傳遞過來的真實型別到底是什麼呢?通過debug會發現,在真正呼叫TestServlet類之前,會經過很多Tomcat中的方法。如下圖所示

注意紅色方框圈中的類,StandardWrapperValue類中的invoke方法225行程式碼如下:

filterChain.doFilter
(request.getRequest(), response.getResponse());

在StandardWrapperValue類中並沒有直接將Request物件與Response物件傳遞給ApplicationFilterChain類的doFilter方法,傳遞的是RequestFacade與ResponseFacade物件,為什麼這麼說呢,看一下request.getRequest()與response.getResponse()方法就真相大白了。

Request類

public HttpServletRequest getRequest() {
        if (facade == null) {
            facade = new RequestFacade(this);
        }
        return facade;
    }

Response類

public HttpServletResponse getResponse() {
        if (facade == null) {
            facade = new ResponseFacade(this);
        }
        return (facade);
    }

可以看到它們返回都是各自的一個門面類,那麼這樣做有什麼好處呢?

Request物件中的很多方法都是內部元件之間相互互動時使用的,比如setComet、setRequestedSessionId等方法(這裡就不一一列舉了)。這些方法並不對外部公開,但是又必須設定為public,因為還需要跟內部元件之間互動使用。最好的解決方法就是通過使用一個Facade類,將與內部元件之間互動使用的方法遮蔽掉,只提供給外部程式感興趣的方法。

如果不使用Facade類,直接傳遞的是Request物件和Response物件,那麼熟悉容器內部運作的程式設計師可以分別把ServletRequest和ServletResponse物件向下轉換為Request和Response,並呼叫它們的公共方法。比如擁有Request物件,就可以呼叫setComet、setRequestedSessionId等方法,這會危害安全性。

3、門面模式優缺點及使用場景

3.1 優點

  • 鬆耦合
    使用者與子系統解耦,遮蔽子系統;可以提高子系統的獨立性;

  • 使用簡單
    簡化使用者與子系統的依賴關係;
    使用者只與門面對接,有統一的入口;不需要知道所有子系統及內部構造;

  • 更好的劃分訪問層次
    通過合理使用Facade,可以幫助我們更好地劃分訪問的層次。有些方法是對系統外的,有些方法是系統內部使用的。把需要暴露給外部的功能集中到門面中,這樣既方便客戶端使用,也很好地隱藏了內部的細節。

3.2 缺點

  • 不規範的程式設計方式
    沒有面向抽象程式設計,而是通過增加中介層,轉換服務提供方的服務介面;
    不符合開閉原則,如果要改東西很麻煩,繼承重寫都不合適。

3.3 注意事項

在層次化結構中,可以使用外觀模式定義系統中每一層的入口。

3.4 使用場景

  1. 為複雜的模組或子系統提供外界訪問的模組。
  2. 子系統相對獨立。
  3. 預防低水平人員帶來的風險。

 

參考文章:

https://www.cnblogs.com/java-my-life/archive/2012/05/02/2478101.html

https://blog.csdn.net/yangspgao/article/details/80602794

https://www.runoob.com/design-pattern/facade-pattern.html

https://www.cnblogs.com/Cubemen/p/10654121.html

相關文章