深入理解設計模式之適配器模式
1. 適配器模式概述
適配器模式(Adapter Pattern)是一種結構型設計模式,它允許將一個類的接口轉換為客戶端所期望的另一個接口。適配器模式使得原本由于接口不兼容而不能一起工作的類能夠協同工作,扮演了"轉換器"的角色。
2. 適配器模式的核心組成
- 目標接口(Target): 客戶端所期望的接口
- 適配者(Adaptee): 需要被適配的類或接口
- 適配器(Adapter): 將適配者接口轉換為目標接口的類
3. 適配器模式的類型
3.1 對象適配器
通過組合方式實現,適配器持有適配者的實例。
3.2 類適配器
通過繼承方式實現,適配器同時繼承適配者和實現目標接口。
4. 適配器模式實現
4.1 對象適配器實現
// 目標接口
interface MediaPlayer {void play(String audioType, String fileName);
}// 適配者接口
interface AdvancedMediaPlayer {void playVlc(String fileName);void playMp4(String fileName);
}// 具體適配者實現
class VlcPlayer implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {System.out.println("Playing vlc file: " + fileName);}@Overridepublic void playMp4(String fileName) {// 不做任何事}
}class Mp4Player implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {// 不做任何事}@Overridepublic void playMp4(String fileName) {System.out.println("Playing mp4 file: " + fileName);}
}// 適配器
class MediaAdapter implements MediaPlayer {private AdvancedMediaPlayer advancedMusicPlayer;public MediaAdapter(String audioType) {if (audioType.equalsIgnoreCase("vlc")) {advancedMusicPlayer = new VlcPlayer();} else if (audioType.equalsIgnoreCase("mp4")) {advancedMusicPlayer = new Mp4Player();}}@Overridepublic void play(String audioType, String fileName) {if (audioType.equalsIgnoreCase("vlc")) {advancedMusicPlayer.playVlc(fileName);} else if (audioType.equalsIgnoreCase("mp4")) {advancedMusicPlayer.playMp4(fileName);}}
}// 客戶端
class AudioPlayer implements MediaPlayer {private MediaAdapter mediaAdapter;@Overridepublic void play(String audioType, String fileName) {// 播放mp3格式音頻文件的內置支持if (audioType.equalsIgnoreCase("mp3")) {System.out.println("Playing mp3 file: " + fileName);}// 通過適配器播放其他格式else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {mediaAdapter = new MediaAdapter(audioType);mediaAdapter.play(audioType, fileName);} else {System.out.println("Invalid media. " + audioType + " format not supported");}}
}// 測試類
public class AdapterPatternDemo {public static void main(String[] args) {AudioPlayer audioPlayer = new AudioPlayer();audioPlayer.play("mp3", "beyond_the_horizon.mp3");audioPlayer.play("mp4", "alone.mp4");audioPlayer.play("vlc", "far_far_away.vlc");audioPlayer.play("avi", "mind_me.avi");}
}
4.2 類適配器實現
// 目標接口
interface Target {void request();
}// 適配者類
class Adaptee {public void specificRequest() {System.out.println("Specific request from Adaptee");}
}// 類適配器
class ClassAdapter extends Adaptee implements Target {@Overridepublic void request() {specificRequest(); // 調用父類方法}
}// 測試類
public class ClassAdapterDemo {public static void main(String[] args) {Target target = new ClassAdapter();target.request();}
}
5. 真實世界適配器模式示例
5.1 舊系統集成案例
// 舊支付系統接口
class LegacyPaymentSystem {public void processPayment(String amount, String account) {System.out.println("Legacy payment processed: " + amount + " from account: " + account);}
}// 新支付接口
interface ModernPaymentGateway {void processPayment(Payment payment);
}// 支付數據傳輸對象
class Payment {private double amount;private String accountId;private String currency;public Payment(double amount, String accountId, String currency) {this.amount = amount;this.accountId = accountId;this.currency = currency;}public double getAmount() {return amount;}public String getAccountId() {return accountId;}public String getCurrency() {return currency;}
}// 支付系統適配器
class PaymentSystemAdapter implements ModernPaymentGateway {private LegacyPaymentSystem legacySystem;public PaymentSystemAdapter() {this.legacySystem = new LegacyPaymentSystem();}@Overridepublic void processPayment(Payment payment) {// 轉換和適配String amount = payment.getAmount() + " " + payment.getCurrency();legacySystem.processPayment(amount, payment.getAccountId());}
}// 客戶端代碼
public class PaymentSystemDemo {public static void main(String[] args) {// 創建新的支付請求Payment payment = new Payment(100.50, "ACC123456", "USD");// 使用適配器處理支付ModernPaymentGateway paymentGateway = new PaymentSystemAdapter();paymentGateway.processPayment(payment);}
}
5.2 第三方庫集成案例
// 第三方圖表庫接口
class ThirdPartyChartLibrary {public void displayChart(String data, String type, int width, int height) {System.out.println("Displaying " + type + " chart with data: " + data);System.out.println("Dimensions: " + width + "x" + height);}
}// 應用程序預期的圖表接口
interface AppChartInterface {void drawChart(ChartData data);
}// 圖表數據
class ChartData {private String dataPoints;private ChartType type;private Dimension size;public ChartData(String dataPoints, ChartType type, Dimension size) {this.dataPoints = dataPoints;this.type = type;this.size = size;}public String getDataPoints() {return dataPoints;}public ChartType getType() {return type;}public Dimension getSize() {return size;}
}// 圖表類型枚舉
enum ChartType {BAR, LINE, PIE
}// 尺寸類
class Dimension {private int width;private int height;public Dimension(int width, int height) {this.width = width;this.height = height;}public int getWidth() {return width;}public int getHeight() {return height;}
}// 圖表庫適配器
class ChartLibraryAdapter implements AppChartInterface {private ThirdPartyChartLibrary chartLibrary;public ChartLibraryAdapter() {this.chartLibrary = new ThirdPartyChartLibrary();}@Overridepublic void drawChart(ChartData data) {// 轉換數據格式String chartType = data.getType().toString().toLowerCase();chartLibrary.displayChart(data.getDataPoints(),chartType,data.getSize().getWidth(),data.getSize().getHeight());}
}// 客戶端代碼
public class ChartAdapterDemo {public static void main(String[] args) {// 創建圖表數據ChartData barChartData = new ChartData("10,25,30,40,50",ChartType.BAR,new Dimension(800, 400));// 使用適配器繪制圖表AppChartInterface chartInterface = new ChartLibraryAdapter();chartInterface.drawChart(barChartData);}
}
6. 雙向適配器模式
// 接口A
interface SystemA {void operationA();
}// 接口B
interface SystemB {void operationB();
}// 系統A實現
class ConcreteSystemA implements SystemA {@Overridepublic void operationA() {System.out.println("System A: performing operation A");}
}// 系統B實現
class ConcreteSystemB implements SystemB {@Overridepublic void operationB() {System.out.println("System B: performing operation B");}
}// 雙向適配器
class TwoWayAdapter implements SystemA, SystemB {private SystemA systemA;private SystemB systemB;public TwoWayAdapter(SystemA systemA, SystemB systemB) {this.systemA = systemA;this.systemB = systemB;}@Overridepublic void operationA() {System.out.println("Adapter: redirecting operationA to operationB");systemB.operationB();}@Overridepublic void operationB() {System.out.println("Adapter: redirecting operationB to operationA");systemA.operationA();}
}// 測試雙向適配器
public class TwoWayAdapterDemo {public static void main(String[] args) {SystemA systemA = new ConcreteSystemA();SystemB systemB = new ConcreteSystemB();// 創建雙向適配器TwoWayAdapter adapter = new TwoWayAdapter(systemA, systemB);// 通過適配器調用SystemA的功能System.out.println("Client uses SystemA interface:");adapter.operationA();// 通過適配器調用SystemB的功能System.out.println("\nClient uses SystemB interface:");adapter.operationB();}
}
7. Java標準庫中的適配器模式
Java標準庫中有多處使用了適配器模式:
import java.io.*;
import java.util.*;public class JavaAdapterExamples {public static void main(String[] args) {// 示例1: InputStreamReader作為適配器// InputStream(適配者) -> Reader(目標接口)try {InputStream is = new FileInputStream("file.txt");Reader reader = new InputStreamReader(is, "UTF-8");int data = reader.read();while(data != -1) {System.out.print((char) data);data = reader.read();}reader.close();} catch (Exception e) {e.printStackTrace();}// 示例2: Arrays.asList()作為適配器// 數組(適配者) -> List(目標接口)String[] namesArray = {"John", "Jane", "Adam"};List<String> namesList = Arrays.asList(namesArray);// 使用List接口的方法System.out.println(namesList.get(1));System.out.println(namesList.contains("Jane"));// 示例3: Collections.enumeration()作為適配器// Collection(適配者) -> Enumeration(目標接口)List<String> list = new ArrayList<>();list.add("Item 1");list.add("Item 2");Enumeration<String> enumeration = Collections.enumeration(list);while(enumeration.hasMoreElements()) {System.out.println(enumeration.nextElement());}}
}
8. 適配器模式的優缺點
優點
- 解耦合: 客戶端與被適配者解耦,通過目標接口進行交互
- 復用性: 可以復用現有的類,即使接口不匹配
- 靈活性: 可以在不修改原有代碼的情況下使用已有功能
- 開閉原則: 符合"開閉原則",不修改原有代碼而擴展功能
缺點
- 復雜性: 引入額外的類,增加系統復雜度
- 性能開銷: 可能引入額外的間接調用,影響性能
- 可讀性: 過多的適配器會使系統難以理解
9. 適配器模式的適用場景
- 集成遺留系統: 當需要集成舊系統或第三方庫時
- 接口不兼容: 當現有接口與客戶端期望的接口不兼容時
- 類庫遷移: 當遷移到新的類庫但不想修改現有代碼時
- 多態接口: 當需要統一多個類的接口時
10. 適配器模式與其他模式的比較
模式 | 適配器模式 | 橋接模式 | 裝飾器模式 | 代理模式 |
---|---|---|---|---|
意圖 | 使不兼容接口兼容 | 將抽象與實現分離 | 動態添加功能 | 控制對象訪問 |
結構 | 轉換接口 | 分離層次結構 | 包裝對象 | 包裝對象 |
側重點 | 接口轉換 | 接口與實現分離 | 功能擴展 | 訪問控制 |
11. 總結
適配器模式是一種強大的結構型設計模式,用于解決接口不兼容問題。它在系統集成、類庫遷移和接口統一等場景中非常有用。通過將一個類的接口轉換為客戶端期望的另一個接口,適配器讓原本不兼容的類能夠協同工作。
適配器模式的兩種主要實現方式——對象適配器和類適配器,為不同場景提供了靈活的解決方案。在實際開發中,對象適配器因其更高的靈活性和更低的耦合度而被更廣泛使用。
適當使用適配器模式可以大大提高代碼的可維護性和可擴展性,特別是在處理遺留系統和第三方庫集成時。但也應注意避免過度使用適配器,以免增加系統的復雜性。