Giới thiệu
Mẫu Adapter (Adapter Pattern) là một mẫu thiết kế thuộc nhóm cấu trúc, đóng vai trò như một cầu nối giữa các thành phần có giao diện không tương thích. Trong thế giới lập trình Java, mẫu này giúp tích hợp các thư viện hoặc hệ thống cũ vào ứng dụng hiện tại mà không cần thay đổi chúng.
Phân tích chi tiết mẫu Adapter
Cấu trúc và thành phần
Mẫu Adapter gồm ba thành phần chính:
- Giao diện mục tiêu (Target Interface): Là giao diện mà client mong muốn sử dụng.
- Lớp được thích ứng (Adaptee): Lớp đã tồn tại nhưng không tương thích với giao diện mục tiêu.
- Lớp adapter (Adapter): Lớp trung gian thực hiện chuyển đổi từ giao diện của Adaptee sang Target.
Các loại Adapter
1. Class Adapter (Sử dụng kế thừa)
// Giao diện mục tiêu
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Lớp cần được thích ứng
class Mp3Player {
public void playMp3(String fileName) {
System.out.println("Playing MP3 file: " + fileName);
}
}
// Adapter sử dụng kế thừa
class AudioAdapter extends Mp3Player implements MediaPlayer {
@Override
public void play(String audioType, String fileName) {
if ("mp3".equalsIgnoreCase(audioType)) {
playMp3(fileName);
}
}
}
2. Object Adapter (Sử dụng composition)
// Adapter sử dụng composition
class MediaAdapter implements MediaPlayer {
private Mp3Player mp3Player;
public MediaAdapter() {
this.mp3Player = new Mp3Player();
}
@Override
public void play(String audioType, String fileName) {
if ("mp3".equalsIgnoreCase(audioType)) {
mp3Player.playMp3(fileName);
}
}
}
3. Interface Adapter (Default Adapter)
// Giao diện đầy đủ
interface AdvancedMedia {
void playMp4();
void playAvi();
void playMkv();
}
// Abstract adapter cung cấp triển khai mặc định
abstract class DefaultMediaAdapter implements AdvancedMedia {
public void playMp4() { /* default implementation */ }
public void playAvi() { /* default implementation */ }
public void playMkv() { /* default implementation */ }
}
// Client chỉ cần override phương thức cần thiết
class VideoPlayer extends DefaultMediaAdapter {
@Override
public void playMp4() {
System.out.println("Playing MP4 video");
}
}
Tình huống áp dụng
1. Tích hợp hệ thống bên thứ ba
// Thư viện bên thứ ba
class ExternalPaymentGateway {
public boolean processTransaction(double amount, String cardNumber) {
// Logic xử lý thanh toán
return true;
}
}
// Giao diện chuẩn của hệ thống
interface PaymentProcessor {
boolean charge(double amount, String cardInfo);
}
// Adapter tích hợp
class PaymentGatewayAdapter implements PaymentProcessor {
private ExternalPaymentGateway gateway;
public PaymentGatewayAdapter(ExternalPaymentGateway gateway) {
this.gateway = gateway;
}
@Override
public boolean charge(double amount, String cardInfo) {
return gateway.processTransaction(amount, cardInfo);
}
}
2. Cải tiến hệ thống legacy
// Hệ thống cũ
class LegacyInventorySystem {
public int checkStockLevel(String productId) {
return 100; // mock data
}
}
// Giao diện mới
interface InventoryService {
boolean isInStock(String productId);
}
// Adapter chuyển đổi
class InventoryAdapter implements InventoryService {
private LegacyInventorySystem legacySystem;
public InventoryAdapter(LegacyInventorySystem system) {
this.legacySystem = system;
}
@Override
public boolean isInStock(String productId) {
return legacySystem.checkStockLevel(productId) > 0;
}
}
So sánh với các mẫu thiết kế khác
Khác biệt với Proxy Pattern
- Proxy: Tập trung vào kiểm soát truy cập đến đối tượng
- Adapter: Tập trung vào chuyển đổi giao diện giữa các thành phần
Khác biệt với Decorator Pattern
- Decorator: Thêm chức năng mới mà không làm thay đổi giao diện
- Adapter: Thay đổi hoàn toàn giao diện để tương thích
Khác biệt với Bridge Pattern
- Bridge: Tách biệt abstraction khỏi implementation
- Adapter: Kết nối hai hệ thống có giao diện khác nhau
Ví dụ thực tế: Hệ thống thanh toán đa nền tảng
// Giao diện thanh toán chuẩn
interface PaymentMethod {
PaymentResult execute(double amount, String currency);
}
// Các hệ thống thanh toán cụ thể
class PayPalService {
public String makePayment(double usdAmount) {
return "PayPal transaction completed";
}
}
class StripeService {
public boolean chargeCustomer(double eurAmount) {
return true;
}
}
// Các adapter chuyên biệt
class PayPalAdapter implements PaymentMethod {
private PayPalService paypal;
public PayPalAdapter(PayPalService paypal) {
this.paypal = paypal;
}
@Override
public PaymentResult execute(double amount, String currency) {
String result = paypal.makePayment(convertToUSD(amount, currency));
return new PaymentResult(result.contains("completed"), result);
}
private double convertToUSD(double amount, String currency) {
// Logic chuyển đổi tiền tệ
return amount * 1.1; // ví dụ đơn giản
}
}
class StripeAdapter implements PaymentMethod {
private StripeService stripe;
public StripeAdapter(StripeService stripe) {
this.stripe = stripe;
}
@Override
public PaymentResult execute(double amount, String currency) {
boolean success = stripe.chargeCustomer(convertToEUR(amount, currency));
return new PaymentResult(success, success ? "Stripe payment OK" : "Failed");
}
private double convertToEUR(double amount, String currency) {
return amount * 0.9;
}
}
// Sử dụng trong hệ thống
class PaymentProcessorDemo {
public static void main(String[] args) {
PaymentMethod paypal = new PayPalAdapter(new PayPalService());
PaymentMethod stripe = new StripeAdapter(new StripeService());
PaymentResult result1 = paypal.execute(100.0, "EUR");
PaymentResult result2 = stripe.execute(100.0, "USD");
System.out.println(result1.getMessage());
System.out.println(result2.getMessage());
}
}
class PaymentResult {
private boolean success;
private String message;
public PaymentResult(boolean success, String message) {
this.success = success;
this.message = message;
}
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
}
Lưu ý khi sử dụng
- Tránh lạm dụng: Chỉ nên dùng khi thật sự cần chuyển đổi giao diện
- Chọn đúng loại adapter: Dựa trên yêu cầu cụ thể của dự án
- Duy trì tính đơn giản: Adapter nên tập trung vào chuyển đổi, không thêm logic nghiệp vụ phức tạp
Kết luận
Mẫu Adapter là công cụ mạnh mẽ giúp giải quyết vấn đề tương thích giữa các thành phần phần mềm. Bằng cách tạo ra các lớp trung gian chuyển đổi giao diện, mẫu này cho phép tích hợp linh hoạt các hệ thống cũ, thư viện bên thứ ba và các thành phần có kiến trúc khác nhau vào ứng dụng hiện tại mà không làm ảnh hưởng đến kiến trúc tổng thể.