Chuyển tới nội dung chính

Adapter Pattern (Mẫu Adapter)

Định nghĩa (Definition)

Adapter chuyển đổi interface của một class thành interface khác mà client mong đợi. Nó cho phép các class hoạt động cùng nhau dù không thể otherwise vì interface không tương thích. Còn được gọi là Wrapper.

Ví dụ Coffee Shop

Quán cà phê có interface IPaymentProcessor riêng. Nhà cung cấp thanh toán third-party mới — StripePay — có API hoàn toàn khác. Thay vì viết lại code, chúng ta xây dựng adapter bao StripePay và làm cho nó giống với interface của chúng ta.

Cấu trúc (Structure)

Target Interface — Những gì Coffee Shop mong đợi

// Interface thanh toán chuẩn của chúng ta
public record PaymentResult(bool Success, string TransactionId, string Message);
public record RefundResult(bool Success, string RefundId, string Message);

public interface IPaymentProcessor
{
PaymentResult ProcessPayment(decimal amount, string currency);
RefundResult Refund(string transactionId);
}

Adaptee — SDK bên thứ ba (Third-Party SDK)

// SDK third-party — không thể sửa đổi code này
public class ChargeRequest
{
public decimal TotalCents { get; set; } // StripePay dùng cents, không phải dollars
public string CurrencyCode { get; set; } = "";
public string CustomerEmail { get; set; } = "";
}

public class ChargeResponse
{
public bool IsSuccessful { get; set; }
public string ChargeId { get; set; } = "";
public string ErrorMessage { get; set; } = "";
}

public class RefundResponse
{
public bool IsSuccessful { get; set; }
public string RefundId { get; set; } = "";
}

// Class SDK third-party
public class StripePaySdk
{
public ChargeResponse Charge(ChargeRequest request) => new()
{
IsSuccessful = true,
ChargeId = $"ch_{Guid.NewGuid():N}"[..16],
};

public RefundResponse IssueRefund(string chargeId) => new()
{
IsSuccessful = true,
RefundId = $"re_{Guid.NewGuid():N}"[..16],
};
}

Adapter — Kết nối hai bên (Bridging the Two)

public class StripePayAdapter : IPaymentProcessor
{
private readonly StripePaySdk _stripePay;

public StripePayAdapter(StripePaySdk stripePay)
{
_stripePay = stripePay;
}

public PaymentResult ProcessPayment(decimal amount, string currency)
{
// Thích ứng: dollars → cents, format của chúng ta → format của họ
var request = new ChargeRequest
{
TotalCents = amount * 100, // Chuyển dollars sang cents
CurrencyCode = currency.ToUpper(),
};

var response = _stripePay.Charge(request);

// Thích ứng: response của họ → response của chúng ta
return new PaymentResult(
Success: response.IsSuccessful,
TransactionId: response.ChargeId,
Message: response.ErrorMessage
);
}

public RefundResult Refund(string transactionId)
{
var response = _stripePay.IssueRefund(transactionId);

return new RefundResult(
Success: response.IsSuccessful,
RefundId: response.RefundId,
Message: response.IsSuccessful ? "Refund processed" : "Refund failed"
);
}
}

Implementation hiện có — Không cần thay đổi

// Implementation thanh toán hiện có của chúng ta
public class CoffeeShopPayment : IPaymentProcessor
{
public PaymentResult ProcessPayment(decimal amount, string currency) =>
new(true, $"local_{Guid.NewGuid():N}"[..16], "Payment processed");

public RefundResult Refund(string transactionId) =>
new(true, $"ref_{Guid.NewGuid():N}"[..16], "Refund processed");
}

Client — Quán cà phê (Coffee Shop)

public class CoffeeShop
{
private readonly IPaymentProcessor _payment;

public CoffeeShop(IPaymentProcessor payment)
{
_payment = payment;
}

public void OrderCoffee(string coffeeName, decimal price)
{
Console.WriteLine($"Ordering {coffeeName} — ${price:F2}");

var result = _payment.ProcessPayment(price, "USD");
Console.WriteLine(result.Success
? $" Payment OK — Transaction: {result.TransactionId}"
: $" Payment FAILED — {result.Message}");
}
}

// Sử dụng — đổi nhà cung cấp thanh toán mà không cần sửa CoffeeShop
var shop = new CoffeeShop(new StripePayAdapter(new StripePaySdk()));
shop.OrderCoffee("Latte", 4.50m);
shop.OrderCoffee("Espresso", 2.50m);

// Output:
// Ordering Latte — $4.50
// Payment OK — Transaction: ch_a1b2c3d4e5f6g7h8
// Ordering Espresso — $2.50
// Payment OK — Transaction: ch_i9j0k1l2m3n4o5p6

Class Adapter vs Object Adapter

Object Adapter (mặc định trong C#)Class Adapter
Cơ chếComposition — bao adapteeKế thừa đa (Multiple Inheritance) — kế thừa từ cả hai
Hỗ trợ C#Có (ưu tiên)Không trực tiếp (không có multiple inheritance)
Linh hoạtHoạt động với subclass của adapteeGắn liền với một concrete adaptee
OverrideKhông thể override hành vi adapteeCó thể override method của adaptee
mẹo

C# không hỗ trợ đa kế thừa class, nên object adapter (composition) là cách tiếp cận tiêu chuẩn. Có thể mô phỏng class adapter với interface + base class, nhưng composition sạch hơn.

Sử dụng thực tế trong .NET (.NET Real-World Usage)

  • StreamReader / StreamWriter — adapt Stream thành read/write dựa trên text
  • HttpClient adapter — bao HttpClient phía sau interface để testability
  • IDbConnection adapter — ADO.NET provider adapt các database khác nhau thành interface chung
  • ASP.NET Core authentication handler — IAuthenticationHandler adapt external OAuth provider

Khi nào sử dụng (When to Use)

  • Cần sử dụng class hiện có nhưng interface không khớp với nhu cầu
  • Muốn tích hợp thư viện third-party mà không gắn code vào API của họ
  • Cần bao legacy code phía sau interface sạch

Khi nào KHÔNG sử dụng (When NOT to Use)

  • Interface đã tương thích — không cần adapter
  • Có thể sửa class gốc — thay đổi trực tiếp
  • Đang thiết kế hệ thống mới — thiết kế interface nhất quán ngay từ đầu

Điểm chính (Key Takeaways)

  • Adapter kết nối interface không tương thích thông qua composition
  • Client code chỉ thấy target interface — adaptee bị ẩn
  • Adapter mới có thể được thêm mà không thay đổi client code hiện có (OCP)
  • Adapter là về việc làm cho mọi thứ hoạt động cùng nhau — không thêm hành vi mới

Câu hỏi phỏng vấn (Interview Questions)

Q: Khác biệt giữa Adapter và Facade là gì? Adapter làm interface hiện tại hoạt động với interface khác — cùng chức năng, hình thức khác. Facade đơn giản hóa subsystem phức tạp thành interface sạch hơn. Adapter là về khả năng tương thích (Compatibility), Facade là về sự đơn giản (Simplicity).

Q: Adapter là class pattern hay object pattern? Cả hai đều tồn tại. Object adapter dùng composition (bao adaptee) — đây là tiêu chuẩn trong C#. Class adapter dùng inheritance — yêu cầu multiple inheritance, ít phổ biến trong C#.

Q: Adapter liên quan đến Dependency Injection như thế nào? Trong ASP.NET, thường inject target interface (IPaymentProcessor) và đăng ký adapter trong DI container:

services.AddScoped<IPaymentProcessor, StripePayAdapter>();

Điều này cho phép toàn bộ app sử dụng abstraction trong khi adapter xử lý integration thực tế.

  • Facade — interface đơn giản cho subsystem
  • Decorator — thêm hành vi, Adapter thay đổi interface
  • Bridge — tách abstraction khỏi implementation ngay từ đầu
  • SOLID — DIP — phụ thuộc vào abstraction (IPaymentProcessor), không phải concrete SDK