享元模式(分離與共享細粒度對象)

0、提綱

目錄:
1、由 HTTP 協(xié)議 聯(lián)想到 對享元模式的思考
2、引入禮盒問題,作為享元模式的逆向思考
3、享元模式的實現(xiàn)
4、享元模式總結(jié)
5、感謝幫助勘誤的簡書作者們

需要查看其它設(shè)計模式描述可以查看我的文章《設(shè)計模式開篇》

1、由 HTTP 協(xié)議 聯(lián)想到 對享元模式的思考

了解享元模式有一段時間了,但到目前為止還沒有自己應(yīng)用過,所以也就一直沒有提筆寫享元模式,怕不能寫出享元模式的精髓。

昨天在讀《HTTP權(quán)威指南》的時候,書中提到了數(shù)據(jù)聚集的 Nagle 算法。

TCP 有一個數(shù)據(jù)流接口,應(yīng)用程序可以通過它將任意尺寸的數(shù)據(jù)放入 TCP 棧中(即使一次只放一個一個字節(jié)也可以)。但是每個 TCP 段中都至少裝載了40個字節(jié)的標記和首部。這意味著如果發(fā)送大量包含少量數(shù)據(jù)的 TCP 分組,網(wǎng)絡(luò)性能就會嚴重下降。

嘗試發(fā)送大量包含少量數(shù)據(jù)的 TCP 分組

Nagle 算法試圖在發(fā)送一個分組之前,將 TCP 數(shù)據(jù)綁定在一起,以提高網(wǎng)絡(luò)效率。Nagle 算法鼓勵發(fā)送全尺寸(LAN 上分組最大約1500字節(jié),WAN 上分組則是幾百字節(jié))的段。只有當其他分組都被確認過后,Nagle 算法才允許發(fā)送非全尺寸的分組。這意味著當其它分組在傳輸過程中,就將已接收到的數(shù)據(jù)緩存起來。只有當掛起的分組被確認,或緩存中積累了足夠發(fā)送一個全尺寸的數(shù)據(jù)時,才允許將緩存的數(shù)據(jù)發(fā)送出去。

啟用 Nagle 算法

拓展:Nagle 算法在現(xiàn)代的服務(wù)器上并不推薦使用,因為它解決了細粒度對象的同時也帶入了不少新的問題。最顯著的是因為引入延遲確認算法,導(dǎo)致的自身被延遲1-200毫秒的情況。

對比上一張圖,差一點:移除了兩個 TCP 分組容器(節(jié)省約80字節(jié)),由三次TCP 段應(yīng)答降至一次應(yīng)答。當然這些都是在 TCP 層次上優(yōu)化,似乎與本期主題(享元模式)扯不上關(guān)系,但細細品味實則不然。

Nagle 算法通過緩沖[buffer](注意不是緩存[cache])數(shù)據(jù)應(yīng)用延遲確認算法,將多個細粒度對象組合成較大的對象放入一個TCP 分段中,以減少創(chuàng)建多余的 TCP 分段。

  • 文章用較多的篇幅說明 Nagle 算法,似乎有些本末倒置。我們暫先忽略Nagle 算法利用緩沖區(qū)將細粒度數(shù)據(jù)拼裝成粗粒度數(shù)據(jù)的過程,著重關(guān)心其結(jié)果 。

  • 如上圖中的三個包含內(nèi)容長度為1字節(jié)的 TCP 分段,采用 Nagle 算法后將三個 TCP 分段的公共首部提取到一個TCP分段中(其實內(nèi)部的 TCP 校驗碼是需要重新計算的),然后將內(nèi)容歸并放入提取出的 TCP 分段中,達到優(yōu)化性能的效用。

注意: 雖然已經(jīng)講了很多,但不得不糾正的一點是 —— Nagle 算法并不是享元模式實現(xiàn),但它確確實實解決了細粒度對象問題。這對正確理解享元模式,起到了不少啟發(fā)作用。

2、引入禮盒問題,作為享元模式的逆向思考

漂亮的禮盒

我們換個視角來看待包裹包裹中的內(nèi)容,生活中的典型例子是出禮品包裝盒禮物。你可以再沒有想好要買什么禮物之前,先買一個漂亮的禮盒(如果我們用心準備禮物的話),用心的你選擇了一只鋼筆作為禮物。

我們很用心的將禮物放進禮盒中系好絲帶 ~~(背景音樂響起) 完整的禮物出現(xiàn)了!

注意:直到目前為止,我們都在拿組合的思想說事。無論是TCP 分段(標記首部&內(nèi)容),還是禮物(禮盒+鋼筆)。因為我們是先有細粒度的對象,再使用細粒度的對象組合成一個粗粒度對象。而享元模式的思想則恰恰相反,它將粗粒度對象劃分成細粒度對象。

3、享元模式的實現(xiàn)

享元模式可以優(yōu)化大量包含重復(fù)數(shù)據(jù)的細粒度對象的場景,典型的場景比如:權(quán)限控制。

比如下表,假設(shè)每字符代表一種權(quán)限,而一長串則表示一串組合權(quán)限?,F(xiàn)在需要你把下面這張表 mapping 成數(shù)據(jù)結(jié)構(gòu),你會怎么干呢?

權(quán)限 用戶
聽說讀寫練看 robert
聽讀練看 staff
聽讀練看寫 jeff
john

一般情況下我們會這么干(如果權(quán)限只是有限的字符串這樣干并沒有什么壞處)。

public class User{
    
    public String name;
    public String permissions;

}

(甲方的新需求):明確告知每個權(quán)限串會非常的長,大約幾 MB大小(為了切換思考角度容易一些,我不得不這樣夸張的說)。

這種時候顯然上面的User結(jié)構(gòu)就無法支撐了,因為我們擁有一大串的User列表。把他們都加載到內(nèi)存中,會造成很大的開銷,這個時候我們不得不思考更好的解決方案。

我們觀察到權(quán)限串有大量的重復(fù)單元,如上表中的每個用戶都有這個權(quán)限。所以我們不妨將permissions拆分成:聽、說、讀、寫、練、看的單個權(quán)限。然后user引用對應(yīng)權(quán)限的對象即可。

public class User{
    
    public String name;
    public Permission[] permissions;

    public User(String name,Permission... permissions){
        this.name = name;
        this.permissions = permission;
    }

}

public class Permission{

    public Permission(String permission){
        this.permission = permission;
    }
    public String permission;
}

public class Main{
    
    public static void main(String[] args){
        
        // 提供權(quán)限
        Permission hear = new Permission("聽");
        Permission say = new Permission("說");
        Permission read = new Permission("讀");
        Permission write = new Permission("寫");
        Permission view = new Permission("看");
        Permission work = new Permission("練");

        // 創(chuàng)建用戶

        User robert = new User("robert",hear,say,read,write,view,work);
        User staff = new User("staff",hear,read,view,work);
        User jeff = new User("jeff",hear,read,write,view,work);
        User john = new User("john",hear);

    }

}

注意:代碼并不是享元模式的實現(xiàn)結(jié)構(gòu)。但我認為已經(jīng)足夠說明問題,相比于第一個方案(String)permissions,下面的方案:權(quán)限部分已經(jīng)做到細粒度的復(fù)用,而那些的確無法復(fù)用的(如:用戶與權(quán)限的綁定關(guān)系)也的確無法復(fù)用了。 當然第二個方案,有太多可優(yōu)化的地方(比如建個權(quán)限工廠)就不再這里體現(xiàn)了。

4、享元模式總結(jié)

享元模式實質(zhì)上將粗粒度的對象拆分成:動 與 靜 的部分。
動:意味著 變化、聯(lián)結(jié)、聯(lián)系,是無法被分割出來的關(guān)系。
靜:意味著 重復(fù)、可模板化的內(nèi)容。

將那些不會變化的內(nèi)容提取出來,也即共享模式的本質(zhì):共享細粒度對象。

是的,全文沒有提到一點如何實現(xiàn)享元模式的結(jié)構(gòu),如果你有需要不妨Google 之。我相信在領(lǐng)悟了其思想的前提下,看任何一篇享元模式實現(xiàn)的文章都是很容易讀懂的。

5、感謝幫助勘誤的簡書作者們

1、感謝簡書作者 遠伯 的勘誤

  • 糾正了 Nagle 算法是基于 buffer 而非 cache 的數(shù)據(jù)延遲算法。
  • 提出 Nagle 的模式與享元模式思想 略有差異的事實。

2、感謝簡書作者九彩拼盤的勘誤

  • 由享元模式聯(lián)想到 配合與邏輯的 抽象層次的享元理解。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容