設計模式的解釋:使用代碼示例的適配器模式

2020-12-14 島上IT

設計模式提供了一種可靠和簡單的方法來遵循經過驗證的設計原則,並編寫結構良好且可維護的代碼。適配器模式是面向對象軟體開發中流行和常用的模式之一。它遵循Robert C. Martin的依賴倒置原則,並使您可以重用現有的類,即使它不實現預期的接口。

如果您對適配器模式進行了一些研究,您會發現它有兩個不同的版本:

使用繼承實現適配器的類適配器模式。使用組合引用適配器內包裝類的實例的對象適配器模式。

您可能知道關於繼承與組合的所有討論。當您需要更改現有代碼時,Composition提供了更大的靈活性並避免了意想不到的副作用。由於這個原因,對象適配器模式是目前較為流行的方法,也是我在本文中將重點討論的方法。

適配器模式

軟體開發中適配器的一般概念與物理世界中的相同。如果你去過不同的國家,你可能會認識到他們中的很多人使用不同形狀的電源插座。很多時候,它們的形狀使得電子設備的插頭不適合。那麼,您如何將手機或筆記本電腦的充電器連接到這些電源插座?

答案很簡單。您將獲得一個適配器,您可以將其放入電源插座,然後將其插入適配器的另一端。適配器更改了插頭的形狀,以便您可以使用電源插座。在那個例子和大多數其他情況下,適配器不提供任何附加功能。它只是讓您將插頭連接到電源插座。

適配器模式通過在接口和現有類之間引入額外的適配器類,將相同的想法應用於面向對象的編程。

適配器類實現期望的接口並保持對要重用的類的對象的引用。由接口定義的方法調用被引用對象上的一個或多個方法並返回預期類型的值。通過這樣做,適配器類通過實現接口來實現預期的契約,並使您能夠重用現有的,不兼容的實現。

讓我們將這個模式應用於一個例子。

使用適配器模式衝泡咖啡

我喜歡以新鮮的一杯咖啡開始我的早晨。唯一的問題是,我需要起床前準備咖啡,然後才可以喝它。當我的鬧鈴響起時,它會自動準備好。所以,讓我們為它構建一個小應用程式。

一款應用來釀造咖啡

FilterCoffeeApp正是這麼做的。它期望FilterCoffeeMachine接口的實現作為構造參數,並在prepareCoffee方法中使用它來衝泡一杯過濾咖啡。

publicclassFilterCoffeeApp {

privateLoggerlog=Logger.getLogger(

FilterCoffeeApp.class.getSimpleName());

privateFilterCoffeeMachinecoffeeMachine;

publicFilterCoffeeApp(FilterCoffeeMachinecoffeeMachine) {

this.coffeeMachine=coffeeMachine;

}

publicCoffeeprepareCoffee() {

Coffeecoffee=this.coffeeMachine.brewCoffee();

log.info("Coffee is ready!");

log.info(" -> "+coffee);

returncoffee;

}

}

FilterCoffeeMachine界面比較簡單。它只是定義了brew咖啡的方法,你可以打電話來做一杯咖啡。

publicinterfaceFilterCoffeeMachine {

CoffeebrewCoffee();

}

這似乎是一個很好的方法,可以讓您在應用程式中使用不同的咖啡機。唯一的要求是代表咖啡機的所有類實現FilterCoffeeMachine接口。

一臺基本的咖啡機

所述BasicCoffeeMachine類實現該接口,並且可以通過使用FilterCoffeeApp

publicclassBasicCoffeeMachineimplementsFilterCoffeeMachine {

privateConfigurationconfig;

privateMap<CoffeeSelection, GroundCoffee>groundCoffee; privateBrewingUnitbrewingUnit;

publicBasicCoffeeMachine(Map<CoffeeSelection, GroundCoffee>coffee) {

this.groundCoffee=coffee;

this.brewingUnit=newBrewingUnit();

this.config=newConfiguration(30, 480);

}

@Override

publicCoffeebrewCoffee() {

// get the coffee

GroundCoffeegroundCoffee=this.groundCoffee.get(

CoffeeSelection.FILTER_COFFEE);

// brew a filter coffee

returnthis.brewingUnit.brew(

CoffeeSelection.FILTER_COFFEE, groundCoffee,

this.config.getQuantityWater());

}

publicvoidaddGroundCoffee(

CoffeeSelectionsel, GroundCoffeenewCoffee)

throwsCoffeeException {

GroundCoffeeexistingCoffee=this.groundCoffee.get(sel);

if (existingCoffee!=null) {

if (existingCoffee.getName().equals(newCoffee.getName())) {

existingCoffee.setQuantity(existingCoffee.getQuantity() +newCoffee.getQuantity());

}

else {

thrownewCoffeeException(

"Only one kind of coffee supported for each CoffeeSelection.");

}

} else {

this.groundCoffee.put(sel, newCoffee);

}

}

}

優質咖啡機

但是如果你得到一臺不具備FilterCoffeeMachine界面的新型咖啡機,會發生什麼?

publicclassPremiumCoffeeMachine {

privateMap<CoffeeSelection, Configuration>configMap;

privateMap<CoffeeSelection, CoffeeBean>beans;

privateGrindergrinder;

privateBrewingUnitbrewingUnit;

publicPremiumCoffeeMachine(Map<CoffeeSelection, CoffeeBean>beans) {

this.beans=beans;

this.grinder=newGrinder();

this.brewingUnit=newBrewingUnit();

this.configMap=newHashMap<>();

this.configMap.put(

CoffeeSelection.FILTER_COFFEE, newConfiguration(30, 480));

this.configMap.put(

CoffeeSelection.ESPRESSO, newConfiguration(8, 28));

}

publicCoffeebrewCoffee(CoffeeSelectionselection)

throwsCoffeeException {

switch (selection) {

caseFILTER_COFFEE:

returnbrewFilterCoffee();

caseESPRESSO:

returnbrewEspresso();

default:

thrownewCoffeeException(

"CoffeeSelection "+selection+" not supported");

}

}

privateCoffeebrewEspresso() {

Configurationconfig=configMap.get(

CoffeeSelection.ESPRESSO);

// grind the coffee beans

GroundCoffeegroundCoffee=this.grinder.grind(

this.beans.get(CoffeeSelection.ESPRESSO),

config.getQuantityCoffee());

// brew an espresso

returnthis.brewingUnit.brew(

CoffeeSelection.ESPRESSO, groundCoffee,

config.getQuantityWater());

}

privateCoffeebrewFilterCoffee() {

Configurationconfig=configMap.get(

CoffeeSelection.FILTER_COFFEE);

// grind the coffee beans

GroundCoffeegroundCoffee=this.grinder.grind(

this.beans.get(CoffeeSelection.FILTER_COFFEE),

config.getQuantityCoffee());

// brew a filter coffee

returnthis.brewingUnit.brew(

CoffeeSelection.FILTER_COFFEE, groundCoffee,

config.getQuantityWater());

}

publicvoidaddCoffeeBeans(CoffeeSelectionsel, CoffeeBeannewBeans) throwsCoffeeException {

CoffeeBeanexistingBeans=this.beans.get(sel);

if (existingBeans!=null) {

if (existingBeans.getName().equals(newBeans.getName())) {

existingBeans.setQuantity(existingBeans.getQuantity() +newBeans.getQuantity());

} else {

thrownewCoffeeException(

"Only one kind of coffee supported for each CoffeeSelection.");

}

} else {

this.beans.put(sel, newBeans);

}

}

}

您可以使用咖啡brewCoffee(CoffeeSelection選擇)拋出CoffeeException的方法PremiumCoffeeMachine準備過濾咖啡或咖啡。如您所見,該方法的名稱與FilterCoffeeMachine接口定義的名稱相同,但方法籤名不兼容。它期望一個參數並聲明一個異常。

PremiumCoffeeMachine代表一個咖啡機,但它沒有實現FilterCoffeeMachine接口。所以,你不能在FilterCoffeeApp中使用它。

我不會改變這個類,以便它實現所需的接口。通常不可能改變現有的類,因為它們是由不同的團隊實現的,或者這些類在其他不包含所需接口的項目中使用。我也不想更改FilterCoffeeMachine接口。該BasicCoffeeMachine實現該接口和我需要改變類每當我更改界面。在這些情況下,最好應用適配器模式。

實現適配器

通過引入實現FilterCoffeeMachine接口並包裝PremiumCoffeeMachine類的適配器類,您可以啟用FilterCoffeeApp以使用咖啡機。

在這個例子中,適配器類需要完成兩項任務:

它必須提供FilterCoffeeMachine接口的實現。brewCoffee方法需要縮小之間的間隙brewCoffee由接口和所定義的方法brewCoffee由實現的方法PremiumCoffeeMachine類。

界面和現有的類沒有太大的不同。這使得適配器類的實現相對簡單。

publicclassFilterCoffeeAdapterimplementsFilterCoffeeMachine {

privateLoggerlog=Logger.getLogger(

FilterCoffeeAdapter.class.getSimpleName());

privatePremiumCoffeeMachinemachine;

publicFilterCoffeeAdapter(PremiumCoffeeMachinemachine) {

this.machine=machine;

}

@Override

publicCoffeebrewCoffee() {

try {

returnmachine.brewCoffee(

CoffeeSelection.FILTER_COFFEE);

} catch (CoffeeExceptione) {

log.severe(e.toString());

returnnull;

}

}

}

正如你在代碼片段中看到的那樣,FilterCoffeeAdapter類實現了FilterCoffeeMachine接口,並且期望PremiumCoffeeMachine對象作為構造器參數。它將該對象保存在專用欄位中,以便它可以在brewCoffee方法中使用它。

brewCoffee方法的實現對於大多數適配器類來說是非常關鍵的,也是最困難的部分。在這種情況下,PremiumCoffeeMachine類提供了一個可以調用以執行任務的方法。但它更靈活,需要一個CoffeeSelection枚舉值來定義它應該生成哪種咖啡。

該方法PremiumCoffeeMachine類會引發CoffeeException如果它得到了一個名為CoffeeSelection不同於值FILTER_COFFEEESPRESSOFilterCoffeeMachine接口的brewCoffee方法不會聲明此異常,您需要在方法實現中處理它。

在這個例子中,沒有完美的方法來做到這一點。您可以編寫日誌消息並返回null,就像我在代碼片段中所做的一樣,也可以拋出RuntimeException

在你的應用程式中,你可能有更好的方法來處理異常。您可能能夠執行重試或觸發不同的業務操作。這將使您的應用程式更健壯並改進適配器的實現。

概要

適配器模式是面向對象程式語言中經常使用的模式。與物理世界中的適配器類似,您可以實現一個類來彌補預期接口和現有類之間的差距。這使您可以重用未實現所需接口的現有類,並使用多個類的功能,否則這些類將不兼容。

適配器模式的一個優點是您不需要更改現有的類或接口。通過引入一個作為接口和類之間的適配器的新類,可以避免對現有代碼進行任何更改。這限制了對軟體組件的更改範圍,並避免了其他組件或應用程式中的任何更改和副作用。

適配器模式提供了依賴倒置設計原理的實現。如果您還不熟悉它,我建議閱讀有關不同的SOLID設計原則。

相關焦點

  • 深度好文:設計模式之——適配器模式
    今天給大家分享一下設計模式中的「適配器模式」。基本介紹適配器模式屬於「結構型模式」的一種。從這點就可以看出該模式是有一定局限性。並且因繼承了被適配的類,所以類的所有方法都暴露在了適配器類中,從而增加了使用成本。優點:由於繼承了「被適配的類」,所以可以根據需求重寫父類的方法,從而增加了代碼的靈活性。
  • Java設計模式之適配器模式和橋接模式詳細解析
    這裡我們用一個簡單的示例來進行說明。 某個視頻播放器,只能播放MP4格式的視頻,但是主流的視頻格式除了MP4,還有AVI、RVMB等,這時就有個軟體,格式工廠用於對視頻格式的轉換(適配器),從而進行播放視頻。這時我們就可以使用適配器模式來進行完成該代碼的編寫。
  • 設計模式之適配器模式(Java實現)
    設計模式之適配器模式(Java實現)小王正在和美女聊天,突然接到領導電話,要讓會議室投影匯報項目情況,只能暫時離開美女去了會議室,到了會議室,發現投影儀的接口和自己電腦中的接口不匹配這個在設計模式中就是適配器模式。適配器模式(Adapter Pattern):將一個類的接口變換成客戶端所期待的另一個接口,從而是原本因為接口不匹配而無法在一起工作的兩個類能夠在一起工作。適配器模式的通用類圖如下:
  • 程式設計師必備設計模式之適配器模式
    昨天是1024程式設計師節,特地停更一天,今天我們來講一個簡單的設計模式,適配器模式。適配器模式,它是一個結構性的設計模式。在我們的生活中,總是有各種各樣的適配器,最常見的,就是你去國外旅遊的時候,通常需要購買轉接插座,因為國內的插座與國外的標準不同,接口不同,你需要一個轉換器。當你使用這個轉換器的時候,你不用關心裏面是如何工作的,只需要關心插頭從什麼標準轉成什麼標準,而不需要關心轉化器如何實現,這便是適配器。
  • java設計模式之適配器模式
    適配器模式(Adapter Pattern)是作為兩個不兼容的接口之間的橋梁。這種類型的設計模式屬於結構型模式,它結合了兩個獨立接口的功能。這種模式涉及到一個單一的類,該類負責加入獨立的或不兼容的接口功能。
  • 設計模式結構性:適配器模式(AdapterPattern)
    適配器模式(Adapter Pattern)是作為兩個不兼容的接口之間的橋梁。這種類型的設計模式屬於結構型模式,它結合了兩個獨立接口的功能。 這種模式涉及到一個單一的類,該類負責加入獨立的或不兼容的接口功能。舉個真實的例子,讀卡器是作為內存卡和筆記本之間的適配器。
  • 「原創」讓設計模式飛一會兒|⑦適配器模式
    今天我們繼續來聊設計模式這個話題。前面已經講過幾個模式,如果沒有閱讀過的朋友可以回顧一下。那麼,今天我們要來聊的是適配器模式,這個模式和上篇講過的代理模式一樣,也是屬於GOF23的結構型模式中的一個模式。
  • Java-今天學學適配器(Adaptor)設計模式
    1.適配器(Adaptor)設計模式1.1.生活中的適配電腦電源適配器:將電腦不能直接使用的220v交流電, 轉換 成電腦可以直接使用的20v直流電;1.2.適配器模式>適配器,主要就是將原本不能或者不方便直接使用的東西,轉換成可以直接或者方便使用的東西;1.3.Java中的適配在Java中,根據適配對象的不同,可以將適配器分為三類:類的適配;接口的適配;對象的適配;
  • 【觀點】「另類」設計模式
    內容如下:任何一個熟悉那本由四個人寫的經典的設計模式書的朋友,應該知道那本書裡的模式都是非常優雅和劃時代的。然而,不幸的是,從那些老代碼中無法提練出這些模式,因為,在出現這些模式前,大家都不會使用模式。因此,這項工作是從大量的代碼中提練出一個模式的目錄。這些模式都有充足和永恆的示例。
  • 設計模式 | Iterator設計模式
    1.0 前言 此文是日本作者結城浩著作的《圖解設計模式》的譯作,原文用java程序編寫,對熟練掌握C++、對java不熟悉的讀者來說,程序讀起來相當費勁。因此本文作者將書中的23個設計模式的程序全部用visual C++6.0和STL庫進行了重新編寫和編譯。
  • 設計模式之——解釋器模式
    簡介今天給大家介紹的設計模式叫做「解釋器模式」,該模式是「行為型設計模式」中的一員。解釋器模式的核心思想是:給定一個語言,定義它的文法的一種表示,並定義一個解釋器,使用該解釋器來解釋語言中的句子。聽完這句話話是不是頓時感覺一臉懵?什麼語言、文法、句子,都是些什麼鬼?別慌讓「菜鳥」來給你分析一波。
  • 常用的Java設計模式
    本文總結了幾個大家常用的Java設計模式,並給出代碼示例。1、單例模式單例(Singleton)模式的定義:指一個類只有一個實例,且該類能自行創建這個實例的一種模式。如果要擴展,則除了修改原來的代碼,沒有第二種途徑,違背開閉原則。在並發測試中,單例模式不利於代碼調試。在調試過程中,如果單例中的代碼沒有執行完,也不能模擬生成一個新的對象。單例模式的功能代碼通常寫在一個類中,如果功能設計不合理,則很容易違背單一職責原則。
  • 【C#】設計模式的學習徵途系列文章目錄(2019版)
    ,參考了《大話設計模式》、《設計模式的藝術》等書籍,並通過C#語言寫了各種模式的代碼示例(已經放到了我的github上並收穫了120+個star)。1  預備篇  下文來自於一位朋友聖傑,帶你通過一個類圖在5分鐘內熟悉UML類圖:2  創建型模式篇創建型模式是一類最常用的設計模式,在軟體開發中應用非常廣泛。創建型模式將對象的創建和使用分離,在使用對象時無需關心對象的創建細節,從而降低系統的耦合度,讓設計方案更易於修改和擴展。
  • 面試官:Mybatis 使用了哪些設計模式?
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫雖然我們都知道有20多個設計模式,但是大多停留在概念層面,真實開發中很少遇到,Mybatis源碼中使用了大量的設計模式,閱讀源碼並觀察設計模式在其中的應用,能夠更深入的理解設計模式。
  • PHP八大設計模式
    設計模式單例模式解決的是如何在整個項目中創建唯一對象實例的問題,工廠模式解決的是如何不通過
  • 你應該了解的5種TypeScript設計模式
    設計模式是解決問題的良好模板,開發人員可以在自己的項目應用這些模式處理需求。現實中應付各種需求的模式數不勝數,一篇文章無法盡述。不過它們可以大致分為三個類別:結構模式,負責處理不同組件(或類)之間的關係,並形成新結構以提供新功能。結構模式的例子有組合(Composite)、適配器(Adapter)和裝飾器(Decorator)。
  • 如何應用觀察者設計模式重構系統中日誌處理功能實現的程序代碼
    軟體項目實訓及課程設計指導——如何應用觀察者設計模式重構系統中的日誌處理功能實現的程序代碼1、GOF設計模式中的觀察者設計模式(1)什麼是觀察者設計模式GOF設計模式中的觀察者設計模式定義了一種解耦「一對多」的依賴關係的編程模式
  • Java Singleton設計模式DEMO
    當您只想擁有給定類的一個實例時,將使用單例設計模式。它是一種創造性的設計模式,我們處理對象的創建。動機和現實世界的例子在面向對象的設計中,某些類只有一個實例非常重要。那是因為它們代表了一種獨特的東西,這是同類產品中的一種。
  • Android設計模式(1)——單例模式
    單例模式使用的場景確保某個類有且只有一個對象的場景,避免產生多個對象消耗過多的資源,或者某種類型對象只應該有且只有一個,例如,創建一個對象需要消耗的資源過多,,如要訪問IO和資料庫等資源,這時就需要考慮使用單例模式。
  • 如何應用GOF設計模式中的構建者模式創建複合對象實例
    軟體項目實訓及課程設計指導——如何應用GOF設計模式中的構建者模式創建複合對象實例1、GOF設計模式中的構建者模式構建者設計模式能夠將一個複雜對象(它一般為組合類)的構建過程與它的表示部件相互分離,使得同樣的構建過程可以創建出不同的表示部件——也就是希望所獲得的目標組合對象