认真学习设计模式之适配器模式(Adapter Pattern)/包装器模式
【1】適配器模式
① 介紹
適配器模式是將一個類的接口,轉換成客戶期望的另一個接口。適配器讓原本接口不兼容的類可以合作無間。
適配器的作用可以簡單通過下圖得知,OO適配器就是將一個接口轉換成另一個接口,以符合客戶的期望。
實際場景應用可以聯想插座適配器,如下圖所示。
適配器模式(Adapter Pattern)將某個類的接口轉換成客戶端期望的另一個接口表示,主的目的是兼容性,讓原本因接口不匹配不能一起工作的兩個類可以協同工作。其別名為包裝器(Wrapper)
適配器模式屬于結構型模式,主要分為三類:類適配器模式、對象適配器模式、接口適配器模式
客戶使用適配器的過程如下:
- ① 客戶通過目標接口調用適配器的方法對適配器發出請求;
- ② 適配器使用被適配者接口把請求轉換成被適配者的一個或多個調用接口。用戶調用適配器轉化出來的目標接口方法,適配器再調用被適配者的相關接口方法
- ③ 客戶接受到調用的結果,但并未察覺這一切是適配器在其轉換作用。
也就是說,客戶和被適配者是解耦的,一個不知道另一個。如果目標接口很大,那么適配器的工作會很多。
比如SpringMVC中DispatcherServlet、HandlerAdapter與Handler之間的關系。DispatcherServlet只需要調用HandlerAdapter的handle方法即可,剩下的工作交給HandlerAdapter處理,而不關心下游細節。更多細節參考博文SpringMVC常見組件之HandlerMapping分析。
使用HandlerAdapter 的原因分析:
可以看到處理器的類型不同,有多重實現方式,那么調用方式就不是確定的,如果需要直接調用Controller 方法,需要調用的時候就得不斷是使用if else 來進行判斷是哪一種子類然后執行。那么如果后面要擴展Controller,就得修改原來的代碼,這樣違背了OCP 原則。
SpringMVC定義了一個適配器接口,使得每一種Controller有一種對應的適配器實現類。適配器代替controller執行相應的方法。擴展controller時,只需要增加一個適配器類就完成了SpringMVC的擴展。
② UML類圖
如下圖所示,這個適配器模式充滿著良好的OO設計原則:使用對象組合,以修改的接口包括被適配者。這種做法還有額外的優點就是被適配者的任何子類都可以搭配著適配器使用。
需要注意的是,這個模式是如何把客戶和接口綁定起來,而不是和實現綁定起來的。我們可以使用數個適配器,每一個都負責轉換不同組的后臺類。或者,也可以加上新的實現,只要他們遵守模板接口就可以。
③ 對象和類的適配器
實際上有兩種適配器:對象適配器和類適配器。類適配器需要多重繼承才能實現,這在Java中不可能的。但是當你使用多重繼承語言的時候,還是可能遇到這樣的需求。
類適配器類圖如下
類適配器不是使用組合來適配被適配者,而是繼承被適配者和目標類。類適配器的優點是不需要重新實現其整個適配者。必要的時候可以覆蓋被適配者的行為。
對象適配器的優點是不僅可以適配某個類,也可以適配該類的任何子類,將工作委托給被適配者進行讓事情更有彈性。
④ 真實案例
如這里編寫一個EnumeratorIterator適配器,將代碼中遺留的枚舉轉換為Iterator使用。
import java.util.Enumeration; import java.util.Iterator;/*** Created by jianggc at 2021/11/2.*/ public class EnumeratorIterator implements Iterator {Enumeration enumeration;public EnumeratorIterator(Enumeration enumeration) {this.enumeration = enumeration;}@Overridepublic boolean hasNext() {return enumeration.hasMoreElements();}@Overridepublic Object next() {return enumeration.nextElement();}@Overridepublic void remove() {throw new UnsupportedOperationException();} }⑤ 裝飾者模式與適配器模式
裝飾著模式重點在包裝,當事情一旦涉及到裝飾著,就表示有一些新的行為或責任要加入到設計中。如mybatis中的CachingExecutor。其擁有目標對象Executor delegate的引用,當執行方法如update時,首先執行裝飾者CachingExecutor自身行為 flushCacheIfRequired(ms);,然后再傳遞調用目標對象delegate的update方法。
@Overridepublic int update(MappedStatement ms, Object parameterObject) throws SQLException {flushCacheIfRequired(ms);return delegate.update(ms, parameterObject);}適配器模式的重點在轉換、解耦。當必須將若干類整合在一起來提供給客戶所期望的接口時,不妨試試適配器。
【2】類適配器
基本介紹:Adapter 類,通過繼承src 類,實現dst 類接口,完成src->dst 的適配。以生活中充電器的例子來講解適配器,充電器本身相當于Adapter,220V 交流電相當于src (即被適配者),我們的目dst(即目標)是5V 直流電。
思路分析如下圖
① 代碼示例
① 適配接口
//適配接口 public interface IVoltage5V {public int output5V(); }② 被適配的類
//被適配的類 public class Voltage220V {//輸出220V的電壓public int output220V() {int src = 220;System.out.println("電壓=" + src + "伏");return src;} }③ 適配器
//適配器類 public class VoltageAdapter extends Voltage220V implements IVoltage5V {@Overridepublic int output5V() {// TODO Auto-generated method stub//獲取到220V電壓int srcV = output220V();int dstV = srcV / 44 ; //轉成 5vreturn dstV;} }④ 客戶端
public class Phone {//充電public void charging(IVoltage5V iVoltage5V) {if(iVoltage5V.output5V() == 5) {System.out.println("電壓為5V, 可以充電~~");} else if (iVoltage5V.output5V() > 5) {System.out.println("電壓大于5V, 不能充電~~");}} }public static void main(String[] args) {System.out.println(" === 類適配器模式 ====");Phone phone = new Phone();phone.charging(new VoltageAdapter()); }② 分析
Java 是單繼承機制,所以類適配器需要繼承src 類這一點算是一個缺點, 因為這要求dst 必須是接口,有一定局限性。
src 類的方法在Adapter 中都會暴露出來,也增加了使用的成本。
由于其繼承了src 類,所以它可以根據需求重寫src 類的方法,使得Adapter 的靈活性增強了。
【3】對象適配器模式
基本思路和類的適配器模式相同,只是將Adapter 類作修改,不是繼承src 類,而是持有src 類的實例,以解決兼容性的問題。即:持有src 類,實現dst 類接口,完成src->dst 的適配。另外根據“合成復用原則”,在系統中盡量使用關聯關系(聚合)來替代繼承關系。對象適配器模式是適配器模式常用的一種。
仍然以上述充電案例分析,思路分析如下圖:
修改適配器類如下:
client如下
public static void main(String[] args) {// TODO Auto-generated method stubSystem.out.println(" === 對象適配器模式 ====");Phone phone = new Phone();phone.charging(new VoltageAdapter(new Voltage220V())); }分析
對象適配器和類適配器其實算是同一種思想,只不過實現方式不同。根據合成復用原則,使用組合替代繼承, 所以它解決了類適配器必須繼承src 的局限性問題,也不再要求dst必須是接口。使用成本更低,更靈活。
【4】接口適配器模式
一些書籍稱為:適配器模式(Default Adapter Pattern)或缺省適配器模式。核心思路就是當不需要全部實現接口提供的方法時,可先設計一個抽象類實現接口,并為該接口中每個方法提供一個默認實現(空方法),那么該抽象類的子類可有選擇地覆蓋父類的某些方法來實現需求。
其適用于一個接口不想使用其所有的方法的情況。
如下所示接口有四個方法:
public interface Interface4 {public void m1();public void m2();public void m3();public void m4(); }在抽象類AbsAdapter 我們將Interface4 的方法進行默認實現。
//在AbsAdapter 我們將 Interface4 的方法進行默認實現 public abstract class AbsAdapter implements Interface4 {//默認實現public void m1() {}public void m2() {}public void m3() {}public void m4() {} }client選擇覆蓋我們需要使用的接口方法,測試如下:
public static void main(String[] args) {AbsAdapter absAdapter = new AbsAdapter() {//只需要去覆蓋我們 需要使用 接口方法@Overridepublic void m1() {System.out.println("使用了m1的方法");}};absAdapter.m1(); }適配器模式的注意事項和細節
三種命名方式,是根據src 是以怎樣的形式給到Adapter(在Adapter 里的形式)來命名的。
- 類適配器:以類給到,在Adapter 里,就是將src 當做類,繼承
- 對象適配器:以對象給到,在Adapter 里,將src 作為一個對象,持有
- 接口適配器:以接口給到,在Adapter 里,將src 作為一個接口,實現
Adapter 模式最大的作用還是將原本不兼容的接口融合在一起工作。實際開發中,實現起來不拘泥于我們講解的三種經典形式
【5】外觀模式
外觀模式提供了一個統一的接口,用來訪問子系統中的一群接口。外觀定義了一個高層接口,讓子系統更容易使用。外觀的意圖是要提供一個簡單的接口,好讓一個子系統更易于使用。
① 如果外觀封裝了子系統的類,那么需要低層功能的客戶如何接觸這些類?
外觀沒有封裝子系統的類,外觀只提供簡化的接口。所以客戶如果覺得有必要,依然可以直接使用子系統的類。這是外觀模式的一個很友好的特征:提供簡化的接口的同時,依然將系統完整的功能暴露出來,以供需要的人使用。
如果需要,可以讓外觀附加聰明功能,讓使用子系統更加方便。當然,也可以根據實際應用,為子系統創建多個外觀。
② 外觀模式的優點
外觀模式不只是簡化接口,也允許你將客戶實現從任何子系統中解耦。
③ 適配器模式與外觀模式
外觀和適配器都可以包裝許多類。適配器模式將一個或多個類接口變成客戶所期望的一個接口。類似地,一個外觀也可以只針對一個擁有復雜接口的類提供簡化的接口。兩種模式的差異不在于它們包裝了一個類,而是在于它們的意圖。適配器模式的意圖是:改變接口符合客戶的期望;而外觀模式的意圖是:提供子系統的一個簡化接口。
④ 最少知識(Least Knowledge)原則
外觀模式完美契合了最少知識原則,客戶只需要知道外觀即可, 不需要知道外觀如何操作、下游子類細節。
當需要使用一個現有的類而其接口并不符合你的需要時,就使用適配器。當需要簡化并統一一個很大的接口或者一群復雜的接口時,使用外觀。適配器改變接口以符合客戶的期望。外觀將客戶從一個復雜的子系統中解耦。
適配器將一個對象保證起來以改變其接口;裝飾者將一個對象包裝起來以增加新的行為和責任;而外觀將一群對象包裝起來以簡化其接口。
總結
以上是生活随笔為你收集整理的认真学习设计模式之适配器模式(Adapter Pattern)/包装器模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GAE构建应用
- 下一篇: 在火狐浏览器中获得borderColor