設(shè)計模式之結(jié)構(gòu)型模式

3、設(shè)計模式之結(jié)構(gòu)型模式

3.1 代理(Proxy)模式

3.1.1 引入案例:

前陣子疫情期間,為了打發(fā)時間竟然入了游戲的坑。一個多月的時間內(nèi)我竟然打怪、升級、砍人、被砍,沉迷游 戲,不可自拔,陷入打怪、升級、打怪、升級......的死循環(huán)中無法逃脫。不過升級還是挺快的哈,小有成就感。這 段時間真真兒的體會了什么叫苦樂參半。參與工會攻城勝利之后超開心,覺得自己還真厲害(可能只是隊友厲害 哈),但是 苦的就是為了升級就要不停的打怪、做任務(wù)(畢竟游戲外掛管的的也太緊了,怕被封號不敢用哈),升 級基本靠自己,夢中還在和大BOSS進行PK。

有了這樣一段經(jīng)歷也比較可貴。咱們作為程序員,能不能把這段打游戲的過程系統(tǒng)化呢?

說來就來:分析、動手。

接口:IGamePlayer,所有網(wǎng)游的玩家(作者也是其中一個哈,包括你嗎?)

實現(xiàn)類:實現(xiàn)游戲愛好者為了玩游戲要執(zhí)行的功能

image.png
image.png
image.png

運行結(jié)果記錄了我的網(wǎng)游生涯。打游戲結(jié)束后網(wǎng)游成癮綜合征體現(xiàn)的淋漓盡致,不想放棄游戲賬號,又不想這樣打 游戲精疲力盡,怎么辦呢?

找代練??!讓他們幫我去打怪、去升級。
定義代練類:代練也不能作弊哦,也是手動打怪升級呢!

image.png
image.png
image.png

看到結(jié)果了吧:沒有任何改變!你沒干活,但是有人幫你干了,你的游戲已經(jīng)不知不覺中升級啦,躺贏變大佬!

這就是代理模式!

3.1.2 代理模式的定義與結(jié)構(gòu)

3.1.2.1 代理模式的定義:

為其他對象提供一種代理以控制這個對象的訪問。這是一個使用頻率非常高的模式。

3.1.2.2 代理模式的結(jié)構(gòu)

代理模式的結(jié)構(gòu)比較簡單,主要是通過定義一個繼承抽象主題的代理來包含真實主題,從而實現(xiàn)對真實主題的訪 問。

1、代理模式的主要角色如下

  1. 抽象主題(Subject)角色:抽象主題類可以是接口或抽象類,是一個普通的業(yè)務(wù)類型定義,聲明真實主題和 代理對象實現(xiàn)的業(yè)務(wù)方法,無特殊要求。
  2. 真實主題(Real Subject)角色:真實主題角色類也叫作被委托角色、被代理角色,實現(xiàn)了抽象主題中的具體 業(yè)務(wù),是代理對象所代表的真實對象,是最終要引用的對象,是業(yè)務(wù)邏輯的具體執(zhí)行者。
  3. 代理(Proxy)角色:也叫做委托類、代理類。他負責(zé)對真實角色的應(yīng)用,把所有抽象主題類定義的方法限制 委托給真實主題角色實現(xiàn),并且在真實主題角色處理完畢前后做預(yù)處理和善后的工作。提供了與真實主題相 同的接口,其內(nèi)部含有對真實主題的引用,它可以訪問、控制或擴展真實主題的功能。

2. 模式的實現(xiàn)

代理模式的實現(xiàn)代碼如下:

image.png
image.png

一個代理類可以代理多個被委托或者被代理者,因此一個代理類具體代理哪個真實主題角色是由場景類決定的。最 簡單的情況就是一個主題類和一個代理類,這是最簡單的代理模式。所以上面的結(jié)構(gòu)中通過構(gòu)造方法傳入被代理對 象就是最簡單的方式。

3.1.3 代理模式的優(yōu)缺點

代理模式的主要優(yōu)點有:

  • 代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用;
  • 代理對象可以擴展目標對象的功能;
  • 代理模式能將客戶端與目標對象分離,在一定程度上降低了系統(tǒng)的耦合度;

其主要缺點是:

  • 在客戶端和目標對象之間增加一個代理對象,會造成請求處理速度變慢;
  • 增加了系統(tǒng)的復(fù)雜度;

3.1.4 代理模式的應(yīng)用場景

在有些情況下,一個客戶不能或者不想直接訪問另一個對象,這時需要找一個中介幫忙完成某項任務(wù),這個中介就 是代理對象。例如,購買火車票不一定要去火車站買,可以通過 12306 網(wǎng)站或者去火車票代售點買。又如買房 子、找保姆、找工作等都可以通過找中介完成。

在軟件設(shè)計中,使用代理模式的例子也很多,例如,spring框架中就使用了動態(tài)代理模式!

  • 遠程代理,這種方式通常是為了隱藏目標對象存在于不同地址空間的事實,方便客戶端訪問。例如,用戶申 請某些網(wǎng)盤空間時,會在用戶的文件系統(tǒng)中建立一個虛擬的硬盤,用戶訪問虛擬硬盤時實際訪問的是網(wǎng)盤空 間。
  • 虛擬代理,這種方式通常用于要創(chuàng)建的目標對象開銷很大時。例如,下載一幅很大的圖像需要很長時間,因 某種計算比較復(fù)雜而短時間無法完成,這時可以先用小比例的虛擬代理替換真實的對象,消除用戶對服務(wù)器 慢的感覺。
  • 安全代理,這種方式通常用于控制不同種類客戶對真實對象的訪問權(quán)限。
  • 智能指引,主要用于調(diào)用目標對象時,代理附加一些額外的處理功能。例如,增加計算真實對象的引用次數(shù) 的功能,這樣當該對象沒有被引用時,就可以自動釋放它。
  • 延遲加載,指為了提高系統(tǒng)的性能,延遲對目標的加載。例如,Hibernate 中就存在屬性的延遲加載和關(guān)聯(lián)表 的延時加載。

3.1.5 代理模式的擴展

在前面介紹的代理模式中,代理類中包含了對真實主題的引用,這種方式存在兩個缺點。

  1. 真實主題與代理主題一一對應(yīng),增加真實主題也要增加代理。
  2. 設(shè)計代理以前真實主題必須事先存在,不太靈活。采用動態(tài)代理模式可以解決以上問題。

什么是動態(tài)代理?動態(tài)代理實在實現(xiàn)階段不關(guān)心代理誰,而是在運行階段才指定哪一個代理對象。

我們繼續(xù)通過打游戲的案例來看動態(tài)代理如何實現(xiàn)。

定義一個實現(xiàn)了InvocationHandler接口的MyInvocationHandler類。其中InvocationHandler接口是JDK提供好的 動態(tài)代理接口。

image.png
image.png

還是代練在幫我打游戲,可是我們既沒有創(chuàng)建代理類,也沒有實現(xiàn)IGamePlayer接口,這就是動態(tài)代理。

如果想讓游戲登錄后發(fā)個信息就更好了,怎么處理呢?

image.png
image.png

如果有人用我的賬號,就發(fā)送一個信息,看看自己的賬號是不是被盜了,其實這就是AOP編程。

動態(tài)代理的實現(xiàn):


image.png
image.png
image.png

上面的DynamicProxy類是一個通用的類,不具有業(yè)務(wù)意義,我們可以來一個更具體的類:

image.png

寫法更簡潔了。該動態(tài)代理只是給出了一個通用的代理框架。大家可以根據(jù)自己的需求設(shè)計自己的AOP框架。

上面是基于JDK的動態(tài)代理,還有基于CGLIB的動態(tài)代理。大家可以自行查閱。

3.2 適配器(Adapter)模式

在現(xiàn)實生活中,經(jīng)常出現(xiàn)兩個對象因接口不兼容而不能在一起工作的實例,這時需要第三者進行適配。例如,講中 文的人同講英文的人對話時需要一個翻譯,用計算機訪問照相機的 SD 內(nèi)存卡時需要一個讀卡器等。

在軟件設(shè)計中也可能出現(xiàn):需要開發(fā)的具有某種業(yè)務(wù)功能的組件在現(xiàn)有的組件庫中已經(jīng)存在,但它們與當前系統(tǒng)的 接口規(guī)范不兼容,如果重新開發(fā)這些組件成本又很高,這時用適配器模式能很好地解決這些問題。

簡單來說就是:讓原來不兼容的兩個接口協(xié)同工作。

3.2.1 適配器模式的定義與結(jié)構(gòu)

3.2.1.1 適配器模式的定義如下:

將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口,使得原本由于接口不兼容而不能一起工作的那些類能一起工作。 適配器模式分為類結(jié)構(gòu)型模式和對象結(jié)構(gòu)型模式兩種,前者類之間的耦合度比后者高,且要求程序員了解現(xiàn)有組件 庫中的相關(guān)組件的內(nèi)部結(jié)構(gòu),所以應(yīng)用相對較少些。

畫個圖表示一下原始的適配器:

image.png

AB兩個物體接口不一致,不能安裝在一起,這個時候引入物體C,要求C既能適應(yīng)A也能適應(yīng)B,這樣三者就可以完 美融合。

image.png

3.2.1.2 模式的結(jié)構(gòu)

適配器模式包含以下主要角色:

  1. 目標(Target)角色:該角色定義把其他類轉(zhuǎn)換為何種接口,也就是我們的期望接口。它可以是抽象類或接 口。
  2. 源(Adaptee)角色:你想把誰轉(zhuǎn)換為目標角色,這個“誰”就是源角色,他是已經(jīng)存在的、運行良好的類或者 對象,經(jīng)過適配器角色的包裝,他會成為一個新的角色。
  3. 適配器(Adapter)角色:是適配器模式的核心橘色,其他兩個角色都是已經(jīng)存在的角色,而適配器角色是需 要新建立的,他的職責(zé)很簡單:把原角色轉(zhuǎn)換為目標角色。如何轉(zhuǎn)換?通過繼承或者類關(guān)聯(lián)的方式。

我們可以按照分析抽取一下通用源碼:

image.png

3.2.2 適配器模式的優(yōu)缺點

該模式的主要優(yōu)點如下。

  • 客戶端通過適配器可以透明地調(diào)用目標接口。
  • 復(fù)用了現(xiàn)存的類,程序員不需要修改原有代碼而重用現(xiàn)有的適配者類。
  • 將目標類和適配者類解耦,解決了目標類和適配者類接口不一致的問題。

其缺點是:對類適配器來說,更換適配器的實現(xiàn)過程比較復(fù)雜。

3.2.3 適配器模式的應(yīng)用實例

案例:話說前幾年,買彩票中大獎啦!所以來了一次說走就走的旅行,去往夢想中的國度--希臘。這里有太多喜歡 的風(fēng)景啦!于是眼睛欣賞的時候,手機拍照根本停不下來。等到回到酒店的時候手機和充電寶寶都沒電了!明天還 要繼續(xù)拍照呢,趕緊充電唄!拿出充電器發(fā)現(xiàn)一個問題,希臘的插座與中國不同,為歐式兩圓孔插座,部分酒店為 內(nèi)嵌式插座,中國電器插頭不能直接使用。沒錯,就是下圖中這樣的!

image.png

怎么辦呢?詢問前臺之后發(fā)現(xiàn)他們很貼心的準備了轉(zhuǎn)換器,于是借來之后順利充電成功。

image.png

我們用代碼實現(xiàn)這個轉(zhuǎn)換過程:

image.png
image.png
image.png

3.2.4 適配器模式的使用場景

(1)其中一個使用的場景是像上面所說的一樣,有兩個接口,你主動的想去連接著兩個接口,寫個適配器,感覺 這種情況也不是很多,因為很多時候都是些一個實體類對象調(diào)用另一個實體類對象。

(2)被動使用的情況,這種情況我可能見得比較多。舉個栗子,比較極端的栗子,你和你同伴一起合作開發(fā),你 同伴寫一個部分,你寫一個部分,現(xiàn)在兩個部分要對接。結(jié)過到對接時,你們發(fā)現(xiàn)兩個人都自定義了接口,而且兩 個人都開發(fā)完了,都不想改,那怎么辦,只能寫一個適配器去適配兩個接口。又或者說你開發(fā)新版本的時候重新定 義了接口,要和舊版本寫適配的時候,為了方便也可以使用適配器模式。

適配器模式(Adapter)通常適用于以下場景。

  • 以前開發(fā)的系統(tǒng)存在滿足新系統(tǒng)功能需求的類,但其接口同新系統(tǒng)的接口不一致。
  • 使用第三方提供的組件,但組件接口定義和自己要求的接口定義不同。

適配器模式是一個補償模式,或者說是一個“補救”模式。通常用來解決接口不相容的問題。一般項目開始的時候用的偏 少。大多數(shù)是在項目的需求不斷變化的時候,技術(shù)為了業(yè)務(wù)服務(wù)的,因此業(yè)務(wù)在變化的時候,對技術(shù)也提出了要求,這些 時候可能就需要這樣的補救模式誕生。

3.3 裝飾(Decorator)模式

3.3.1 案例引入

大家上學(xué)的時候有沒有遇到過這樣的情況:

老師:考試成績出來了,大家把成績單拿回家給家長看并請家長簽字明天帶回來。

我:......(按慣例,一頓“竹筍炒肉”是少不了了)

我們用程序來描繪一下這個過程:

image.png
image.png

這樣的成績單肯定少不了一頓打,所以我就開始想辦法,能不能把成績單裝飾一下,不讓我的成績看起來那么像挨 揍的成績呢?于是我想了以下辦法(注意:修改成績造假那是不可以滴):

  • 匯報一下最高成績:其實是本次考試成績都不好,最高分也都是七十多分,如果我報告了最高成績,老爸一 看我成績跟最高成績對比,就會覺得我這個分數(shù)還能接受;
  • 匯報一下班級排名:告訴老爸我再全班排名32,當然我不會告訴他我們班考試人數(shù)也就是38個人,因為有好 幾個同學(xué)因為生病沒參加考試。成績單上沒有這些信息,我得趁此機會免遭一頓打啊。

說做就做,于是修改一下原有的程序展示這個過程。最簡單的辦法就是添加一個子類,重寫show方法:

image.png
image.png

我們通過繼承的方式解決了這個問題。老爸看后沒揍我就簽字了。但是現(xiàn)實的情況可能有很多種:

  • 看完最高成績和我的成績,直接簽名了,不看后面的排名
  • 老爸要先看排名,再看我的成績,再看最高成績

現(xiàn)實中遇到不同的情況怎么辦呢?要繼續(xù)擴展多少個子類呢?這個還是需要裝飾的條件比較少的情況,如果條件多 了,不是2個,而是20個,那要有多少個子類?。?!

這就是繼承解決該問題帶來的問題,繼承帶來的類越多,后期維護的成本也會越高。那是不是應(yīng)該優(yōu)化一下咱們的 設(shè)計呢?所有,咱們就定義一批專門負責(zé)裝飾的類,然后根據(jù)實際情況來決定是否需要進行裝飾。

程序?qū)崿F(xiàn):


image.png
image.png
image.png

運行結(jié)果:


image.png

實現(xiàn)的結(jié)果一樣!而如果我還需要其他的修飾條件,我們只需要實現(xiàn)Decotator類就可以啦!這就是裝飾模式!

3.3.2 裝飾模式的定義與結(jié)構(gòu)

3.3.2.1 裝飾模式的定義:

指在不改變現(xiàn)有對象結(jié)構(gòu)的情況下,動態(tài)地給該對象增加一些職責(zé)(即增加其額外功能)的模式,它屬于對象結(jié)構(gòu) 型模式。

3.3.2.2 裝飾模式的結(jié)構(gòu)與實現(xiàn)

通常情況下,擴展一個類的功能會使用繼承方式來實現(xiàn)。但繼承具有靜態(tài)特征,耦合度高,并且隨著擴展功能的增 多,子類會很膨脹。如果使用組合關(guān)系來創(chuàng)建一個包裝對象(即裝飾對象)來包裹真實對象,并在保持真實對象的 類結(jié)構(gòu)不變的前提下,為其提供額外的功能,這就是裝飾模式的目標。下面來分析其基本結(jié)構(gòu)和實現(xiàn)方法。

1. 模式的結(jié)構(gòu)

裝飾模式主要包含以下角色。

  1. 抽象構(gòu)件(Component)角色:是一個抽象類或者接口,定義最核心的對象,也就是最原始的對象,例如上 面的成績單。
  2. 具體構(gòu)件(Concrete Component)角色:實現(xiàn)抽象構(gòu)件,通過裝飾角色為其添加一些職責(zé)。
  3. 抽象裝飾(Decorator)角色:一般是一個抽象類,繼承抽象構(gòu)件,實現(xiàn)其抽象方法,里面不一定有抽象的方 法,在他的屬性里一般都會有一個private變量指向Component抽象構(gòu)件 。
  4. 具體裝飾(ConcreteDecorator)角色:實現(xiàn)抽象裝飾的相關(guān)方法,并給具體構(gòu)件對象添加附加的責(zé)任。

2. 模式的實現(xiàn)

裝飾模式的實現(xiàn)代碼如下:

image.png
image.png
image.png

3.3.3 裝飾模式的優(yōu)缺點

裝飾(Decorator)模式的主要優(yōu)點有:

  • 采用裝飾模式擴展對象的功能比采用繼承方式更加靈活。
  • 可以設(shè)計出多個不同的具體裝飾類,創(chuàng)造出多個不同行為的組合。

其主要缺點是:裝飾模式增加了許多子類,如果過度使用會使程序變得很復(fù)雜。

3.3.4 裝飾模式的應(yīng)用場景

裝飾模式通常在以下幾種情況使用。

  • 當需要給一個現(xiàn)有類添加附加職責(zé),而又不能采用生成子類的方法進行擴充時。例如,該類被隱藏或者該類 是終極類或者采用繼承方式會產(chǎn)生大量的子類。
  • 當需要通過對現(xiàn)有的一組基本功能進行排列組合而產(chǎn)生非常多的功能時,采用繼承關(guān)系很難實現(xiàn),而采用裝 飾模式卻很好實現(xiàn)。
  • 當對象的功能要求可以動態(tài)地添加,也可以再動態(tài)地撤銷時。
  • 需要為一批的兄弟類進行改裝或者加裝功能的時候,可以首選裝飾模式。

3.3.5 裝飾模式在java中的應(yīng)用

裝飾模式在 java語言中的最著名的就是 Java I/O 標準庫的設(shè)計了。

例如,InputStream 的子類 FilterInputStream,OutputStream 的子類 FilterOutputStream,Reader 的子類 BufferedReader 以及 FilterReader,還有 Writer 的子類 BufferedWriter、FilterWriter 以及 PrintWriter 等,它 們都是抽象裝飾類。

下面代碼是為 FileReader 增加緩沖區(qū)而采用的裝飾類 BufferedReader 的例子:

BufferedReader in=new BufferedReader(new FileReader("filename.txtn));
String s=in.readLine();

3.4 亨元(Flyweight)模式

在面向?qū)ο蟪绦蛟O(shè)計過程中,有時會面臨要創(chuàng)建大量相同或相似對象實例的問題。創(chuàng)建那么多的對象將會耗費很多 的系統(tǒng)資源,它是系統(tǒng)性能提高的一個瓶頸。

例如:String常量池、數(shù)據(jù)庫連接池、緩沖池等等都是享元模式的應(yīng)用,所以說享元模式是池技術(shù)的重要實現(xiàn)方 式。

比如我們每次創(chuàng)建字符串對象時,都需要創(chuàng)建一個新的字符串對象的話,內(nèi)存開銷會很大,所以如果第一次創(chuàng) 建了字符串對象“adam“,下次再創(chuàng)建相同的字符串”adam“時,只是把它的引用指向”adam“,這樣就實現(xiàn) 了”adam“字符串再內(nèi)存中的共享。

再舉個例子:網(wǎng)絡(luò)聯(lián)機下棋的時候,一臺服務(wù)器連接了多個玩家,如果我們每個棋子都要創(chuàng)建對象,那一盤棋可 能就有上百個對象產(chǎn)生,玩家多點的話,因為內(nèi)存空間有限,一臺服務(wù)器就難以支持了,所以這里要使用享元模 式,將棋子對象減少到幾個實例。

3.4.1 享元模式的定義與結(jié)構(gòu)

3.4.1.1 享元模式的定義:

運用共享技術(shù)來有效地支持大量細粒度對象的復(fù)用。它通過共享已經(jīng)存在的對象來大幅度減少需要創(chuàng)建的對象數(shù) 量、避免大量相似類的開銷,從而提高系統(tǒng)資源的利用率。

3.4.1.2. 享元模式的結(jié)構(gòu)

享元模式中存在以下兩種狀態(tài):

  • 內(nèi)部狀態(tài),即不會隨著環(huán)境的改變而改變的可共享部分;
  • 外部狀態(tài),指隨環(huán)境改變而改變的不可以共享的部分。享元模式的實現(xiàn)要領(lǐng)就是區(qū)分應(yīng)用中的這兩種狀態(tài), 并將外部狀態(tài)外部化。下面來分析其基本結(jié)構(gòu)和實現(xiàn)方法。

1、享元模式的主要角色有如下。

image.png
  • 抽象享元角色(Flyweight):簡單理解就是一個產(chǎn)品的抽象類,同時定義出對象的外部狀態(tài)和內(nèi)部狀態(tài)的接口 或者實現(xiàn)。
  • 具體享元(Concrete Flyweight)角色:實現(xiàn)抽象享元角色中所規(guī)定的接口。
  • 非享元(Unsharable Flyweight)角色:是不可以共享的外部狀態(tài),它以參數(shù)的形式注入具體享元的相關(guān)方法 中。
  • 享元工廠(Flyweight Factory)角色:負責(zé)創(chuàng)建和管理享元角色。當客戶對象請求一個享元對象時,享元工 廠檢査系統(tǒng)中是否存在符合要求的享元對象,如果存在則提供給客戶;如果不存在的話,則創(chuàng)建一個新的享 元對象。

2. 享元模式的實現(xiàn)

享元模式的參考實現(xiàn)代碼如下:

image.png
image.png

3.4.2 享元模式的優(yōu)缺點:

優(yōu)點:
大大減少應(yīng)用程序創(chuàng)建的對象,相同對象只要保存一份,,降低程序內(nèi)存的占用,這降低了系統(tǒng)中對象的數(shù)量,從 而降低了系統(tǒng)中細粒度對象給內(nèi)存帶來的壓力。

缺點:

  1. 為了使對象可以共享,需要將一些不能共享的狀態(tài)外部化,這將增加程序的復(fù)雜性。
  2. 讀取享元模式的外部狀態(tài)會使得運行時間稍微變長。

3.4.3 享元模式的應(yīng)用實例

實現(xiàn)一個生活中的場景:開部門會議

image.png
image.png
image.png

3.4.4 享元模式的應(yīng)用場景

  1. 系統(tǒng)中存在大量相同或相似的對象,這些對象耗費大量的內(nèi)存資源。
  2. 細粒度的對象都具備比較接近的外部狀態(tài),而且內(nèi)部狀態(tài)與環(huán)境無關(guān),也就是說對象沒有特定的身份。
  3. 需要緩沖池的場景

3.5 外觀模式

3.6 橋接模式

3.7 組合模式

?著作權(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)容