結構型模式:適配器模式

什么是適配器模式?

適配器模式(Adapter Pattern)是一種常用的結構型設計模式,它的主要作用是將一個類的接口轉換成客戶端期望的另一個接口。就像現實生活中的各種轉接頭一樣,適配器模式使得原本因接口不兼容而無法一起工作的類能夠協同合作。

想象一下,你有一個美國制造的電器,插頭是兩孔扁頭,但你在中國旅行,插座是三孔。這時,你需要一個電源轉換器(適配器)來解決這個問題。在軟件設計中,適配器模式正是解決這類"接口不匹配"問題的優雅解決方案。

適配器模式也常被稱為包裝器(Wrapper)模式,因為它就像一個包裝紙,將原本不兼容的接口"包裝"起來,使其能與目標接口兼容。

適配器模式的類型

適配器模式主要有兩種實現方式:

1. 類適配器

類適配器通過多重繼承(在Java中通過繼承被適配類并實現目標接口)實現適配。類適配器使用的是繼承機制。

2. 對象適配器

對象適配器通過組合方式實現適配,即在適配器中持有被適配對象的實例。對象適配器使用的是組合機制。

適配器模式的結構

下面是適配器模式的UML類圖,它清晰地展示了這種設計模式的結構:

適配器模式UML類圖

在這個結構中,Target(目標接口)是客戶端所期望的接口,Adaptee(被適配者)是需要被適配的類或接口,而Adapter(適配器)則是將Adaptee轉換成Target的類。

適配器模式的基本實現

對象適配器模式實現

對象適配器使用組合方式,將被適配的類的實例包裝在適配器中:

// 目標接口:客戶端期望使用的接口
public interface Target {void request();  // 客戶端期望調用的方法
}// 被適配的類:已經存在的、接口不兼容的類
public class Adaptee {// 被適配類的方法與目標接口不兼容public void specificRequest() {System.out.println("適配者的特殊請求方法");}
}// 對象適配器類:通過組合方式包含被適配對象
public class ObjectAdapter implements Target {// 持有一個被適配類的引用private Adaptee adaptee;// 通過構造函數注入被適配對象public ObjectAdapter(Adaptee adaptee) {this.adaptee = adaptee;}// 實現目標接口的方法,在內部調用被適配對象的方法@Overridepublic void request() {System.out.println("對象適配器: 轉換請求");// 調用被適配對象的方法完成真正的功能adaptee.specificRequest();}
}// 客戶端代碼
public class Client {public static void main(String[] args) {// 創建被適配對象Adaptee adaptee = new Adaptee();// 創建適配器對象,將被適配對象傳入Target adapter = new ObjectAdapter(adaptee);System.out.println("客戶端通過適配器調用請求...");// 客戶端通過目標接口調用方法,實際上最終會調用被適配對象的方法adapter.request();}
}

在這個對象適配器實現中,我們定義了一個Target接口作為客戶端期望使用的接口,而Adaptee是一個已經存在但接口不兼容的類。ObjectAdapter充當適配器角色,它實現了Target接口,同時在內部持有一個Adaptee實例。當客戶端調用適配器的request()方法時,適配器會將調用轉發給AdapteespecificRequest()方法,從而實現接口的適配。這樣,客戶端就能夠通過目標接口間接使用被適配類的功能,而不必關心它們之間的接口差異。

類適配器模式實現

類適配器使用繼承方式,同時繼承被適配類并實現目標接口:

// 目標接口:客戶端期望使用的接口
public interface Target {void request();  // 客戶端期望調用的方法
}// 被適配的類:已經存在的、接口不兼容的類
public class Adaptee {// 被適配類的方法與目標接口不兼容public void specificRequest() {System.out.println("適配者的特殊請求方法");}
}// 類適配器:通過繼承被適配類并實現目標接口
public class ClassAdapter extends Adaptee implements Target {// 實現目標接口的方法@Overridepublic void request() {System.out.println("類適配器: 轉換請求");// 直接調用父類(被適配類)的方法specificRequest();}
}// 客戶端代碼
public class Client {public static void main(String[] args) {// 使用類適配器Target adapter = new ClassAdapter();System.out.println("客戶端通過適配器調用請求...");// 客戶端通過目標接口調用方法adapter.request();}
}

類適配器與對象適配器的主要區別在于實現方式。類適配器通過繼承Adaptee類,直接獲得了被適配類的方法,而無需像對象適配器那樣持有被適配對象的引用。ClassAdapter同時繼承了Adaptee類并實現了Target接口,當客戶端調用request()方法時,適配器可以直接調用繼承自AdapteespecificRequest()方法。類適配器的優點是實現更加簡潔,但由于Java只支持單繼承,這種方式會受到繼承體系的限制,靈活性不如對象適配器。

實際應用示例:電源適配器

讓我們用一個現實世界中的例子——電源適配器——來展示適配器模式的應用:

// 美國標準電源接口(110V)
interface USPowerSource {void supplyPowerAt110V();  // 提供110V電力的方法
}// 歐洲標準電源接口(220V)
interface EUPowerSource {void supplyPowerAt220V();  // 提供220V電力的方法
}// 美國電源實現
class USPowerSupply implements USPowerSource {@Overridepublic void supplyPowerAt110V() {System.out.println("提供110V的電力");}
}// 歐洲電源實現
class EUPowerSupply implements EUPowerSource {@Overridepublic void supplyPowerAt220V() {System.out.println("提供220V的電力");}
}// 電子設備接口(期望220V)
interface ElectronicDevice {void powerOn();  // 設備開機方法
}// 歐洲電子設備(需要220V電源)
class EuropeanDevice implements ElectronicDevice {private EUPowerSource powerSource;  // 依賴歐洲標準電源public EuropeanDevice(EUPowerSource powerSource) {this.powerSource = powerSource;}@Overridepublic void powerOn() {System.out.println("歐洲設備啟動中...");// 使用歐洲標準電源powerSource.supplyPowerAt220V();System.out.println("歐洲設備工作正常!");}
}// 電源適配器:將110V轉為220V(對象適配器模式)
class PowerAdapter implements EUPowerSource {private USPowerSource usPowerSource;  // 持有美國電源對象public PowerAdapter(USPowerSource usPowerSource) {this.usPowerSource = usPowerSource;}// 實現歐洲電源接口方法@Overridepublic void supplyPowerAt220V() {System.out.println("適配器轉換中: 110V -> 220V");// 調用美國電源方法usPowerSource.supplyPowerAt110V();System.out.println("電壓轉換完成,輸出220V");}
}// 測試代碼
public class PowerAdapterDemo {public static void main(String[] args) {// 在美國使用歐洲設備System.out.println("=== 在美國使用歐洲電器 ===");// 創建美國電源USPowerSource usPower = new USPowerSupply();// 創建適配器(將美國電源適配為歐洲電源)EUPowerSource adapter = new PowerAdapter(usPower);// 創建歐洲設備并使用適配器供電ElectronicDevice europeanDevice = new EuropeanDevice(adapter);// 啟動設備europeanDevice.powerOn();System.out.println("\n=== 在歐洲使用歐洲電器(無需適配器)===");// 歐洲電源EUPowerSource euPower = new EUPowerSupply();// 直接使用歐洲電源ElectronicDevice deviceInEurope = new EuropeanDevice(euPower);deviceInEurope.powerOn();}
}

運行結果

=== 在美國使用歐洲電器 ===
歐洲設備啟動中...
適配器轉換中: 110V -> 220V
提供110V的電力
電壓轉換完成,輸出220V
歐洲設備工作正常!=== 在歐洲使用歐洲電器(無需適配器)===
歐洲設備啟動中...
提供220V的電力
歐洲設備工作正常!

這個例子模擬了現實世界中的電源適配器場景。我們有美國標準的110V電源和歐洲標準的220V電源,而歐洲電子設備需要220V電源才能正常工作。當我們在美國(只有110V電源)使用歐洲設備時,需要一個電源適配器來進行轉換。適配器PowerAdapter在內部調用美國電源的方法,然后進行必要的轉換,最終提供歐洲設備所需的220V電源。這樣,歐洲設備就可以通過適配器在美國使用了。而在歐洲使用歐洲設備時,由于電源標準匹配,就不需要適配器了。

這個例子非常直觀地展示了適配器的作用:讓不兼容的接口(110V和220V)能夠協同工作,就像現實中的電源轉換器一樣。

實際應用示例:舊系統集成

在企業應用中,系統集成是適配器模式的一個典型應用場景。下面我們來看一個舊系統集成的例子:

// 舊的用戶信息系統接口
class LegacyUserSystem {// 舊系統返回格式化的字符串public String fetchUserData(String userId) {// 模擬從舊系統獲取用戶數據,格式為:USER:ID:姓名:性別:年齡:地址return String.format("USER:%s:張三:男:30:北京", userId);}
}// 新系統期望的用戶模型
class User {private String id;        // 用戶IDprivate String name;      // 用戶姓名private String gender;    // 性別private int age;          // 年齡private String address;   // 地址// 構造函數public User(String id, String name, String gender, int age, String address) {this.id = id;this.name = name;this.gender = gender;this.age = age;this.address = address;}// 重寫toString方法,方便輸出用戶信息@Overridepublic String toString() {return "User{" +"id='" + id + '\'' +", name='" + name + '\'' +", gender='" + gender + '\'' +", age=" + age +", address='" + address + '\'' +'}';}// Getters 和 Setters省略
}// 新的用戶服務接口(新系統期望的接口)
interface UserService {User getUser(String userId);     // 獲取用戶信息void saveUser(User user);        // 保存用戶信息
}// 適配器:將舊系統集成到新系統(對象適配器模式)
class UserSystemAdapter implements UserService {private LegacyUserSystem legacySystem;  // 持有舊系統的引用public UserSystemAdapter(LegacyUserSystem legacySystem) {this.legacySystem = legacySystem;}// 實現新接口的獲取用戶方法@Overridepublic User getUser(String userId) {// 從舊系統獲取數據String userData = legacySystem.fetchUserData(userId);// 解析舊系統返回的字符串數據并轉換為User對象String[] parts = userData.split(":");if (parts.length < 5) {throw new RuntimeException("無效的用戶數據格式");}String id = parts[1];String name = parts[2];String gender = parts[3];int age = Integer.parseInt(parts[4]);String address = parts[5];// 返回新系統能理解的用戶對象return new User(id, name, gender, age, address);}// 實現新接口的保存用戶方法@Overridepublic void saveUser(User user) {// 這里可以實現將新系統User對象保存到舊系統的邏輯System.out.println("將用戶保存到舊系統:" + user);// 在實際應用中,應當調用舊系統的API來保存用戶}
}// 新系統的用戶管理類
class UserManager {private UserService userService;  // 依賴用戶服務接口public UserManager(UserService userService) {this.userService = userService;}// 顯示用戶信息的方法public void displayUserInfo(String userId) {try {// 通過用戶服務獲取用戶信息User user = userService.getUser(userId);System.out.println("用戶信息:" + user);} catch (Exception e) {System.out.println("獲取用戶信息失敗:" + e.getMessage());}}
}// 測試代碼
public class SystemIntegrationDemo {public static void main(String[] args) {// 創建舊系統實例LegacyUserSystem legacySystem = new LegacyUserSystem();// 創建適配器,將舊系統適配到新接口UserService adapter = new UserSystemAdapter(legacySystem);// 新系統使用適配后的服務UserManager userManager = new UserManager(adapter);// 通過新系統接口訪問舊系統數據System.out.println("=== 使用適配器訪問舊系統 ===");userManager.displayUserInfo("12345");}
}

在這個系統集成的例子中,我們有一個舊的用戶信息系統LegacyUserSystem,它以字符串格式返回用戶數據。而新系統需要使用結構化的User對象。為了解決這個接口不匹配的問題,我們創建了一個適配器UserSystemAdapter,它實現了新系統期望的UserService接口,同時在內部調用舊系統的API。

適配器負責將舊系統返回的字符串數據解析并轉換為新系統需要的User對象。這樣,新系統的UserManager就可以通過UserService接口與適配器交互,而不需要知道后面實際上是舊系統在提供數據。通過這種方式,適配器模式使得系統集成變得優雅且松耦合,新系統不需要直接適應舊系統的接口,而是通過適配器間接地使用舊系統的功能。

適配器模式在Java標準庫中的應用

Java標準庫中有許多適配器模式的例子,了解這些例子有助于我們理解適配器模式在實際開發中的應用:

Java的InputStreamReaderOutputStreamWriter類就是典型的適配器模式應用。InputStreamReader將字節流(InputStream)適配為字符流(Reader),解決了字節與字符的轉換問題。同樣,OutputStreamWriter將字節輸出流(OutputStream)適配為字符輸出流(Writer)。這樣,開發者就可以用統一的字符流接口處理不同編碼的輸入輸出,而不必關心底層的字節處理細節。

Arrays.asList()方法也是一個適配器的例子,它將數組適配為List集合,使數組可以使用集合的方法。通過這個適配器,我們可以將一個固定長度的數組轉換為一個List接口的對象,從而能夠使用集合框架提供的豐富功能。

另外,Collections.list()將舊式的Enumeration適配為現代的List集合,這是為了兼容早期Java版本的代碼而設計的適配器。Java XML綁定API中的XmlAdapter則是在XML數據與Java對象之間進行轉換的適配器,它使得XML序列化和反序列化過程更加靈活可控。

適配器模式的優缺點

優點

優點說明
增加了類的透明性客戶端通過目標接口與適配器交互,不需要了解適配器背后的實現細節
提高了類的復用性通過適配器,原本不兼容的類可以在新環境中得到復用
靈活性和可擴展性可以引入更多適配器支持更多類型的適配者,系統更易于擴展
遵循開閉原則無需修改現有代碼,通過添加適配器來滿足新需求
結構清晰適配器的職責明確,系統結構清晰易于理解和維護

缺點

缺點說明
增加系統復雜度引入適配器會增加系統中的類和間接層,使系統略微復雜化
可能需要修改多個適配器當適配者接口發生變化時,所有相關適配器可能都需要更新
可能導致性能損失通過中間層轉換可能帶來輕微的性能損失
調試復雜度增加當出現問題時,可能需要調試適配層而非業務層,增加排錯難度

最后的一丟丟總結

適配器模式是一種強大的結構型設計模式,它能夠將不兼容的接口轉換成客戶端期望的接口,讓原本無法一起工作的類能夠協同工作。通過適配器模式,我們可以集成新系統和遺留系統,重用現有的類,使第三方庫和現有系統無縫協作,以及在不修改現有代碼的情況下滿足新的接口需求。

適配器模式有兩種主要實現方式:類適配器(通過繼承)和對象適配器(通過組合)。在實際應用中,對象適配器更為常用,因為它更加靈活且符合"組合優于繼承"的設計原則。雖然適配器模式增加了一定的間接性和復雜性,但它提供的接口轉換能力使得系統更加靈活、可擴展,特別是在系統集成和演化過程中,適配器模式能夠發揮重要作用。

當你面臨接口不兼容的問題,或需要集成多個系統時,不妨考慮使用適配器模式。它就像現實生活中的轉接頭一樣,能夠讓不兼容的部分和諧地工作在一起,讓系統更加靈活和可維護。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/78099.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78099.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/78099.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

AI Agent認知框架(ReAct、函數調用、計劃與執行、自問自答、批判修正、思維鏈、思維樹詳解和對比,最后表格整理總結

以下是主流AI Agent認知框架的詳細說明、對比及表格總結&#xff1a; 1. 各認知框架詳解 (1) ReAct (Reasoning Action) 定義&#xff1a;結合推理&#xff08;Reasoning&#xff09;和行動&#xff08;Action&#xff09;的循環過程。核心機制&#xff1a; 模型先推理&…

特征存儲的好處:特征存儲在機器學習開發中的優勢

隨著企業尋求提升機器學習生產力和運營能力 (MLOps),特征存儲 (Feature Store) 的普及度正在迅速提升。隨著 MLOps 技術的進步,特征存儲正成為機器學習基礎設施的重要組成部分,幫助企業提升模型的性能和解釋能力,并加速新模型與生產環境的集成。這些存儲充當集中式存儲庫,…

SPRING-AI 官方事例

springAI 關于最近看了很多SpringAi&#xff0c;閱讀很多代碼都感覺特別陌生 SpringAI依賴的springBoot版本都是3.3以上, 以及很多SpringAi都是依賴JDK版本最低17, 并且出現了很多新關鍵字例如 var,record 等寫法, 煙花繚亂得lambda 表達式&#xff0c; 到處都是使用build 構…

Visual Studio Code 使用tab鍵往左和往右縮進內容

使用VSCode寫東西&#xff0c;經常遇到多行內容同時縮進的情況&#xff0c;今天寫文檔的時候就碰到&#xff0c;記錄下來&#xff1a; 往右縮進 選中多行內容&#xff0c;點tab鍵&#xff0c;會整體往右縮進&#xff1a; 往左縮進 選中多行內容&#xff0c;按shifttab&am…

機器學習(7)——K均值聚類

文章目錄 1. K均值&#xff08;K-means&#xff09;聚類是什么算法&#xff1f;2. 核心思想2. 數學目標3. 算法步驟3.1. 選擇K個初始質心&#xff1a;3.2.迭代優化3.3. 重復步驟2和步驟3&#xff1a; 4. 關鍵參數5. 優缺點6. 改進變種7. K值選擇方法8. Python示例9. 應用場景10…

爬蟲案例-爬取某企數據

文章目錄 1、準備要爬取企業名稱數據表2、爬取代碼3、查看效果 1、準備要爬取企業名稱數據表 企業名稱紹興市袍江王新國家庭農場紹興市鄭杜糧油專業合作社紹興市越城區興華家庭農場紹興市越城區銳意家庭農場紹興市越城區青甸畈家庭農場紹興市袍江王新國家庭農場紹興市袍江月明…

足球 AI 智能體技術解析:從數據采集到比賽預測的全鏈路架構

一、引言 在足球運動數字化轉型的浪潮中&#xff0c;AI 智能體正成為理解比賽、預測賽果的核心技術引擎。本文從工程實現角度&#xff0c;深度解析足球 AI 的技術架構&#xff0c;涵蓋數據采集、特征工程、模型構建、實時計算到決策支持的全鏈路技術方案&#xff0c;揭示其背后…

怎么配置一個kubectl客戶端訪問多個k8s集群

怎么配置一個kubectl客戶端訪問多個k8s集群 為什么有的客戶端用token也訪問不了k8s集群&#xff0c;因為有的是把~/.kube/config文件&#xff0c;改為了~/.kube/.config文件&#xff0c;文件設置成隱藏文件了。 按照kubectl的尋找配置的邏輯&#xff0c;kubectl找不到要訪問集群…

[QMT量化交易小白入門]-四十六、年化收益率118%的回測參數,如何用貪心算法挑選50個兩兩相關性最小的ETF組合

本專欄主要是介紹QMT的基礎用法,常見函數,寫策略的方法,也會分享一些量化交易的思路,大概會寫100篇左右。 QMT的相關資料較少,在使用過程中不斷的摸索,遇到了一些問題,記錄下來和大家一起溝通,共同進步。 文章目錄 相關閱讀準備工作安裝所需庫導入所需模塊下載所有ETF數…

幾何編碼:啟用矢量模式地理空間機器學習

在 ML 模型中使用點、線和多邊形&#xff0c;將它們編碼為捕捉其空間屬性的向量。 自地理信息系統 (GIS) 誕生之初&#xff0c;“柵格模式”和“矢量模式”之間就存在著顯著的區別。在柵格模式下&#xff0c;數據以值的形式呈現在規則的網格上。這包括任何形式的圖像&#xff0…

Leetcode98、230:二叉搜索樹——遞歸學習

什么是二叉搜索樹&#xff1a;右子樹節點 > 根節點 > 左子樹節點&#xff0c; 二叉搜索樹中的搜索&#xff0c;返回給定值val所在的樹節點 終止條件為傳進來的節點為空、或者節點的值 val值&#xff0c;返回這個節點&#xff1b; 單程遞歸邏輯&#xff1a;定義一個resu…

每天學一個 Linux 命令(30):cut

??可訪問網站查看,視覺品味拉滿: http://www.616vip.cn/30/index.html cut 命令用于從文件或輸入流中提取文本的特定部分(如列、字符或字節位置)。它常用于處理結構化數據(如 CSV、TSV)或按固定格式分割的文本。以下是詳細說明和示例: 命令格式 cut [選項] [文件...]…

Tauri 2.3.1+Leptos 0.7.8開發桌面應用--Sqlite數據庫選中數據的表格輸出

在前期工作的基礎上&#xff08;Tauri 2.3.1Leptos 0.7.8開發桌面應用--Sqlite數據庫的寫入、展示和選擇刪除_tauri leptos sqlite 選擇刪除-CSDN博客&#xff09;&#xff0c;實現將選中的數據實時用表格展示出來&#xff0c;效果如下&#xff1a; 1. 后臺invoke調用命令 Tau…

使用Tauri 2.3.1+Leptos 0.7.8開發桌面小程序匯總

近期斷斷續續學習了Rust編程&#xff0c;使用Tauri 2.3.1Leptos 0.7.8開發了一個自用的桌面小程序。Win10操作系統&#xff0c;使用VS Code及rust analyzer插件搭建的開發環境&#xff0c;后期開始使用Roo Code綁定DeepSeek API 輔助編程&#xff0c;對我這個初學者編程幫助很大…

考研英一學習筆記

2024 年全國碩士研究生招生考試 英語&#xff08;一&#xff09;試題 &#xff08;科目代碼&#xff1a;201&#xff09; Section Ⅰ Use of English Directions: Read the following text. Choose the best word(s) for each numbered blank and mark A, B, C or D on the ANS…

【技術筆記】Cadence實現Orcad與Allegro軟件交互式布局設置

【技術筆記】Cadence實現Orcad與Allegro軟件交互式布局設置 更多內容見專欄&#xff1a;【硬件設計遇到了不少問題】、【Cadence從原理圖到PCB設計】 在做硬件pcb設計的時候&#xff0c;原理圖選中一個元器件&#xff0c;希望可以再PCB中可以直接選中。 為了達到原理圖和PCB兩兩…

卷積神經網絡(CNN)詳解

文章目錄 引言1.卷積神經網絡&#xff08;CNN&#xff09;的誕生背景2.卷積神經網絡&#xff08;CNN&#xff09;介紹2.1 什么是卷積神經網絡&#xff1f;2.2 卷積神經網絡&#xff08;CNN&#xff09;的基本特征2.2.1 局部感知&#xff08;Local Connectivity&#xff09;2.2.…

8051單片機所有Keil C51匯編偽指令和C語言關鍵字大全

8051單片機所有Keil C51匯編偽指令和C語言關鍵字大全 作者將狼才鯨創建日期2025-04-21 CSDN閱讀地址&#xff1a;8051單片機所有Keil匯編偽指令和C語言關鍵字的詳細解釋 8051單片機所有Keil匯編偽指令和C語言關鍵字的詳細解釋&#xff0c;在Keil已安裝文件夾D:\Keil_v5\C51\H…

機器視覺的智能手機屏貼合應用

在智能手機制造領域&#xff0c;屏幕貼合工藝堪稱"微米級的指尖芭蕾"。作為影響觸控靈敏度、顯示效果和產品可靠性的關鍵工序&#xff0c;屏幕貼合精度直接決定了用戶體驗。傳統人工對位方式已無法滿足全面屏時代對極窄邊框和超高屏占比的嚴苛要求&#xff0c;而Mast…