Nội dung
Mục tiêu
Iterator là một mẫu thiết kế hành vi cho phép bạn duyệt qua các phần tử của một tập hợp mà không để lộ biểu diễn cơ bản của nó (danh sách, ngăn xếp, cây, v.v.).
Vấn đề
Tập hợp là một trong những kiểu dữ liệu được sử dụng nhiều nhất trong lập trình. Tuy nhiên, một tập hợp chỉ là một vùng chứa cho một nhóm các đối tượng.
Hầu hết các tập hợp lưu trữ các phần tử của chúng trong các danh sách đơn giản. Tuy nhiên, một số trong số chúng dựa trên ngăn xếp, cây, đồ thị và các cấu trúc dữ liệu phức tạp khác.
Nhưng bất kể tập hợp được cấu trúc như thế nào, nó phải cung cấp một số cách truy cập các phần tử của nó để mã khác có thể sử dụng các phần tử này. Cần có một cách để duyệt từng phần tử của tập hợp mà không cần truy cập lặp lại các phần tử giống nhau.
Điều này nghe có vẻ là một công việc dễ dàng nếu bạn có một bộ sưu tập dựa trên một danh sách. Bạn chỉ cần lặp lại tất cả các phần tử. Nhưng làm cách nào để bạn duyệt tuần tự các phần tử của một cấu trúc dữ liệu phức tạp, chẳng hạn như một cái cây? Ví dụ, một ngày nào đó bạn có thể cảm thấy ổn với việc duyệt qua chiều sâu của một cái cây. Tuy nhiên, ngày hôm sau, bạn có thể yêu cầu duyệt theo chiều rộng. Và tuần tới, bạn có thể cần một thứ gì đó khác, chẳng hạn như quyền truy cập ngẫu nhiên vào các phần tử trrong cây.
Việc thêm ngày càng nhiều thuật toán duyệt vào tập hợp dần dần làm lu mờ trách nhiệm chính của nó, đó là lưu trữ dữ liệu hiệu quả. Ngoài ra, một số thuật toán có thể được điều chỉnh cho một ứng dụng cụ thể, vì vậy việc gộp chúng vào một lớp tập hợp chung sẽ rất kỳ lạ.
Mặt khác, mã client được cho là hoạt động với các tập hợp khác nhau thậm chí có thể không quan tâm đến cách chúng lưu trữ các phần tử của chúng. Tuy nhiên, vì tất cả các tập hợp đều cung cấp các cách khác nhau để truy cập các phần tử của chúng, bạn không có lựa chọn nào khác ngoài việc ghép mã của mình với các lớp tập hợp cụ thể.
Giải pháp
Ý tưởng chính của mẫu Iterator là trích xuất hành vi duyệt của một tập hợp thành một đối tượng riêng biệt được gọi là trình lặp (iterator).
Ngoài việc triển khai chính thuật toán, một đối tượng trình lặp còn đóng gói tất cả các chi tiết duyệt, chẳng hạn như vị trí hiện tại và số lượng phần tử còn lại cho đến cuối. Do đó, một số trình lặp có thể duyệt qua cùng một tập hợp cùng một lúc, độc lập với nhau.
Thông thường, các trình lặp cung cấp một phương thức chính để tìm nạp các phần tử của tập hợp. Client có thể tiếp tục chạy phương thức này cho đến khi nó không trả về bất kỳ thứ gì, có nghĩa là trình lặp đã duyệt qua tất cả các phần tử.
Tất cả các trình lặp phải triển khai cùng một giao diện. Điều này làm cho mã client tương thích với bất kỳ loại tập hợp nào hoặc bất kỳ thuật toán duyệt nào miễn là có một trình lặp thích hợp. Nếu bạn cần một cách đặc biệt để duyệt qua một tập hợp, bạn chỉ cần tạo một lớp trình lặp mới mà không cần phải thay đổi tập hợp hoặc ứng dụng client.
Cấu trúc
- Giao diện Iterator khai báo các hoạt động cần thiết để duyệt qua một tập hợp: tìm nạp phần tử tiếp theo, truy xuất vị trí hiện tại, bắt đầu lặp lại, v.v.
- Concrete Iterator thực hiện các thuật toán cụ thể để duyệt qua một tập hợp. Đối tượng trình lặp sẽ tự theo dõi tiến trình duyệt. Điều này cho phép một số trình lặp duyệt qua cùng một tập hợp độc lập với nhau.
- Giao diện Collection khai báo một hoặc nhiều phương thức để có được các trình lặp tương thích với tập hợp. Lưu ý rằng kiểu trả về của các phương thức phải được khai báo dưới dạng giao diện Iterator để các tập hợp cụ thể có thể trả về các loại trình lặp khác nhau.
- Concrete Collection trả về các phiên bản mới của một lớp trình lặp cụ thể mỗi khi client yêu cầu. Bạn có thể tự hỏi, phần còn lại của mã của tập hợp ở đâu? Đừng lo lắng, nó phải ở cùng một lớp. Chỉ là những chi tiết này không quan trọng đối với mẫu thực tế, vì vậy chúng tôi sẽ bỏ qua chúng.
- Client làm việc với cả tập hợp Collection và trình lặp Iterator thông qua giao diện của chúng. Bằng cách này, client không được kết hợp với các lớp cụ thể, cho phép bạn sử dụng các tập hợp và trình lặp khác nhau với cùng một mã client.
Thông thường, client không tự tạo trình lặp mà thay vào đó lấy chúng từ tập hợp. Tuy nhiên, trong một số trường hợp nhất định, client có thể tạo trực tiếp; ví dụ, khi xác định trình lặp đặc biệt của riêng nó.
Ưu và nhược điểm
Ưu
- Single Responsibility Principle. Bạn có thể làm sạch mã client và các tập hợp bằng cách trích xuất các thuật toán duyệt cồng kềnh thành các lớp riêng biệt.
- Open/Closed Principle. Bạn có thể triển khai các kiểu tập hợp và trình lặp mới và chuyển chúng vào mã hiện có mà không vi phạm bất kỳ điều gì.
- Bạn có thể lặp song song trên cùng một tập hợp vì mỗi đối tượng trình lặp chứa trạng thái lặp riêng của nó.
- Vì lý do tương tự, bạn có thể trì hoãn một lần lặp lại và tiếp tục nó khi cần.
Nhược
- Áp dụng mẫu có thể là một việc làm quá mức cần thiết nếu ứng dụng của bạn chỉ hoạt động với các tập hợp đơn giản.
- Sử dụng trình lặp có thể kém hiệu quả hơn so với việc duyệt qua trực tiếp các phần tử của một số tập hợp chuyên biệt.
Để lại một bình luận