Tài liệu

Hướng dẫn Thiết kế Adapter Pattern

Huy Erick

Trong bài viết này, chúng ta sẽ cùng tìm hiểu về Thiết kế Adapter Pattern - một mẫu thiết kế có cấu trúc, cách triển khai, ví dụ, ưu điểm, nhược điểm và ứng dụng...

Trong bài viết này, chúng ta sẽ cùng tìm hiểu về Thiết kế Adapter Pattern - một mẫu thiết kế có cấu trúc, cách triển khai, ví dụ, ưu điểm, nhược điểm và ứng dụng của nó.

Trong cuộc sống hàng ngày, chúng ta thường gặp các tình huống cần sử dụng Adapter Pattern khi ta tích hợp một thư viện hoặc module từ bên ngoài vào chương trình của mình và muốn điều chỉnh interface của nó sao cho phù hợp với chương trình. Hãy tưởng tượng bạn đang phát triển một ứng dụng hỗ trợ vẽ đồ thị và giải các bài toán đồ thị. Trên internet, đã có sẵn nhiều thư viện hỗ trợ cho việc này. Tuy nhiên, thư viện mà bạn lấy từ internet chỉ hỗ trợ vẽ điểm bằng tọa độ cực. Trong khi đó, bạn muốn sử dụng tọa độ Descartes để vẽ điểm. Bài toán đặt ra là: "Làm sao để đáp ứng yêu cầu của client, tức là nhận tham số đầu vào cho hàm vẽ là tọa độ tung độ và hoành độ, mà vẫn sử dụng thư viện chỉ hỗ trợ tọa độ cực?".

Để giải quyết bài toán này, chúng ta có thể tạo ra một adapter - một lớp trung gian - giúp thư viện trên mạng hiểu được yêu cầu của client. Adapter sẽ nhận tham số đầu vào là tọa độ Descartes, sau đó chuyển đổi thành tọa độ cực và gọi hàm vẽ điểm của thư viện. Kết quả là client có thể giao tiếp với thư viện mà không cần biết rằng có một adapter đang hoạt động ở giữa. Adapter Pattern giúp chúng ta tách biệt việc chuyển đổi interface với logic chính của chương trình.

Phân loại và Giới thiệu

Adapter Pattern thuộc nhóm Structural Pattern, gồm những pattern cung cấp các phương pháp để lắp ráp các đối tượng và lớp thành những cấu trúc phức tạp hơn đồng thời giữ cấu trúc này linh hoạt và hiệu quả. Theo định nghĩa của GOF, Adapter Pattern chuyển đổi interface của một class thành interface mà client yêu cầu. Adapter cho phép các class làm việc cùng nhau dù chúng có interface không tương thích với nhau.

Ở cuộc sống hàng ngày, chúng ta thường thấy adapter dưới dạng thiết bị kết nối giữa các phích cắm và ổ điện khác loại. Adapter trong lập trình hướng đối tượng cũng có chức năng tương tự như thế giới thực. Nó nhận một interface và thích ứng để trở thành một interface mà client cần sử dụng.

Ví dụ về ứng dụng vẽ đồ thị

Một trong những tình huống phải dùng đến Adapter Pattern là khi bạn tích hợp một thư viện bên ngoài vào chương trình và muốn chỉnh sửa interface của nó cho phù hợp với chương trình của mình. Trong ví dụ này, bạn lấy trên mạng một thư viện thực hiện chức năng vẽ tọa độ một điểm trên đồ thị. Nhưng khổ nỗi thư viện đó chỉ hỗ trợ biểu diễn điểm bằng tọa độ cực. Trong khi client lại quen thuộc với tọa độ Descartes. Bài toán đặt ra là: "Làm sao để đáp ứng yêu cầu của client, tức là nhận tham số đầu vào cho hàm vẽ là tọa độ tung độ và hoành độ, mà vẫn sử dụng thư viện chỉ hỗ trợ tọa độ cực?".

Để giải quyết bài toán này, bạn tạo ra một class đặc biệt, gọi là adapter, là thằng trung gian giúp thư viện trên mạng hiểu được yêu cầu của client. Adapter nhận tọa độ Descartes đầu vào, sau đó chuyển đổi sang tọa độ cực và gọi hàm vẽ điểm của thư viện. Kết quả là client cứ tưởng được graph interface xử lý, nhưng class thực sự xử lý vẫn là PolarGraph. Và class trung gian điều hướng chính là adapter.

Các thành phần của Adapter Pattern

Adapter Pattern gồm các thành phần sau:

  1. Client làm việc trực tiếp thông qua interface mà adapter triển khai.
  2. Adapter triển khai interface mà client sử dụng, chuyển đổi các yêu cầu của client thành những yêu cầu cụ thể mà adaptee hiểu.
  3. Adaptee là class sẽ đáp ứng yêu cầu của client nhưng hiểu theo cách mà adapter truyền lại. Adaptee có thể là những class có nhiều dependencies hoặc có nhiều class khác cần dùng tới.
  4. Adapter giữ reference đến adaptee, thường được truyền vào thông qua constructor của adapter.

Cách triển khai Adapter Pattern

Adapter Pattern được triển khai theo các bước sau:

  1. Client gửi yêu cầu thông qua interface.
  2. Tạo một lớp adapter để triển khai interface của client.
  3. Lớp adapter giữ reference đến adaptee, thông qua constructor hoặc setter injection.
  4. Adapter triển khai các phương thức của interface của client, chuyển đổi dữ liệu nếu cần, và gọi các phương thức tương ứng của adaptee.
  5. Client nhận được kết quả mà họ muốn mà không biết sự tồn tại của adapter.

Ví dụ mã nguồn

Dưới đây là một ví dụ về cách triển khai Adapter Pattern bằng ngôn ngữ C#. Trong ví dụ này, chúng ta sử dụng Adapter Pattern để thực hiện ứng dụng vẽ đồ thị được đề cập ở trên:

using System;

namespace Adapter
{
    public interface IGraph
    {
        void Point(double x, double y);
    }

    class PolarGraph
    {
        public void Point(double r, double t)
        {
            Console.WriteLine("Polar Coordinate Point: P(" + r + ", " + t + ")");
        }
    }

    class PolarGraphAdapter : IGraph
    {
        private readonly PolarGraph polarGraph;

        public PolarGraphAdapter(PolarGraph polarGraph)
        {
            this.polarGraph = polarGraph;
        }

        public void Point(double x, double y)
        {
            double r = Math.Sqrt(x * x + y * y);
            double t = Math.Atan2(y, x);

            polarGraph.Point(r, t);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            PolarGraph polarGraph = new PolarGraph();
            IGraph graph = new PolarGraphAdapter(polarGraph);

            graph.Point(3, 4);
            // Output: Polar Coordinate Point: P(5, 0.9272952180016122)
        }
    }
}

Ưu điểm và Nhược điểm

Adapter Pattern có nhiều ưu điểm như:

  • Cho phép nhiều đối tượng với các interface khác nhau giao tiếp với nhau.
  • Phân tách việc chuyển đổi interface với logic chính của chương trình.
  • Adapter có thể được mở rộng mà không ảnh hưởng đến client.
  • Giúp tuân thủ các quy tắc lập trình hướng đối tượng như Open/Closed Principle và Liskov Substitution Principle.

Tuy nhiên, Adapter Pattern cũng có một số nhược điểm như:

  • Đôi khi tất cả các yêu cầu phải được chuyển tiếp thông qua adapter, làm tăng phần nào độ phức tạp của code.
  • Đôi khi không thể thích nghi các method của các interface khác nhau với nhau, gây ra exception.

Kết luận

Adapter Pattern là một mẫu thiết kế có cấu trúc, giúp chúng ta nhanh chóng và linh hoạt trong việc tương thích giữa các interface không tương thích với nhau. Pattern này đã được ứng dụng rộng rãi trong nhiều trường hợp, đặc biệt là trong việc nâng cấp và mở rộng các hệ thống cũ.

Nguồn tham khảo:

  1. Erich Gamma, John Vlissides, Richard Helm, Ralph Johnson - Design Patterns: Elements of Reusable Object-Oriented Software
  2. Alexander Shvets (refactoring.guru) - Dive Into Design Patterns
  3. Elisabeth Freeman, Kathy Sierra - Head First Design Patterns
1