Tổng quan về Iterator Design Pattern


Iterator được thiết kế cho phép xử lý nhiều loại tập hợp khác nhau bằng cách truy cập những phần tử của tập hợp với cùng một phương pháp, cùng một cách thức định sẵn, mà không cần phải hiểu rõ về những chi tiết bên trong của những tập hợp này.


I. Giới thiệu

Iterator hay còn gọi là Cursor là một mẫu thiết kế thuộc nhóm hành vi (Behavioral Pattern).

Iterator được thiết kế cho phép xử lý nhiều loại tập hợp khác nhau bằng cách truy cập những phần tử của tập hợp với cùng một phương pháp, cùng một cách thức định sẵn, mà không cần phải hiểu rõ về những chi tiết bên trong của những tập hợp này.

Nói cách khác, một Iterator được thiết kế cho phép xử lý nhiều loại tập hợp khác nhau bằng cách truy cập những phần tử của tập hợp với cùng một phương pháp, cùng một cách thức định sẵn

Ý tưởng thiết kế này là một trong những kỹ thuật được gọi là “đơn trách nhiệm – Single responsibility principle (SRP)” – một lớp chỉ có duy nhất một công việc để làm. Hãy suy nghĩ rằng tập hợp duy trì các phần tử, một iterator cung cấp cách thức làm việc với các phần tử đó. Đó cũng là lý do tại sao những Iterator có thể làm việc được trong các tập hợp khác nhau.

II. Mục đích ra đời

Tập hợp (collection) là một trong những kiểu dữ liệu được sử dụng nhiều nhất trong việc lập trình. Có rất nhiều loại Collection khác nhau từ đơn giản như List đến phức tạp như Stack, Queue, Tree, đồ thị,... 

Các ứng dụng và phần mềm chúng ta code thường sử dụng rất nhiều Collection khác nhau. Tuy nhiên, vì mỗi Collection đều cung cấp các cách khác nhau để truy cập các phần tử, nên bạn không có lựa chọn nào khác ngoài việc sử dụng những Collection cụ thể vào những tình huống cụ thể.

Vì bất cập đó mà Iterator Pattern ra đời, với mục đích tạo ra 1 interface chung, cho phép chúng ta duyệt qua các phần tử của Collection mà không cần biết về kiểu của Collection.

Ý tưởng chính của Iterator Pattern là gom tất cả những hành vi (như cách truy xuất vị trí hiện tại, cho biết bao nhiều phần tử còn lại,...) của một Collection thành một đối tượng riêng biệt được gọi là Iterator. 

Tất cả các Iterator sẽ phải implement cùng một Interface. Điều này làm cho source code tương thích với bất kỳ loại Collection nào hoặc bất kỳ thuật toán so sánh nào miễn là có một iterator thích hợp. Nếu bạn cần một cách đặc biệt để duyệt qua Collection, bạn chỉ cần tạo một iterator, mà không cần phải thay đổi Collection hoặc source code.

III. Kiến trúc

Các thành phần trong mô hình:

  • Iterator: là một interface hay abstract class, khai báo các hoạt động cần thiết để tra so sánh 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ại lặp lại, v.v.
  • Concrete Iterator: implement các phương thức của Iterator, giữ index khi duyệt qua các phần tử. Cho phép một số trình vòng lặp đi qua cùng một bộ sưu tập độc lập với nhau.
  • Collection Interface: khai báo một hoặc nhiều phương thức để nhận được các Iterator tương thích với collection. 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 Iterator Interface để các collection cụ thể có thể trả về các các loại Iterator.
  • Concrete Collections: trả về các phiên bản mới của một lớp Concrete Iterator cụ thể mỗi khi client yêu cầu.
  • Client: đối tượng sử dụng Iterator Pattern, nó yêu cầu một iterator từ một đối tượng collection để duyệt qua các phần tử mà nó giữ. Các phương thức của iterator được sử dụng để truy xuất các phần tử từ collection theo một trình tự thích hợp.

 

IV. Source code minh họa với C#

Tạo Iterator để duyệt qua các item trong một List theo step.

  • Tạo Item model
    //Collection Item
    public class Item
    {
        string name;
        public Item(string name)
        {
            this.name = name;
        }
        public string Name
        {
            get { return name; }
        }
    }   

 

  • Khai báo Iterator Interface.
    Ít nhất nó phải có một phương thức để tìm nạp phần tử tiếp theo từ một collection. Nhưng để thuận tiện, bạn có thể thêm một số phương thức khác, chẳng hạn như tìm nạp phần tử trước đó, theo dõi vị trí hiện tại và kiểm tra thời điểm kết thúc vòng lặp.
//Iterator
    public interface IAbstractIterator
    {
        Item First();
        Item Next();
        bool IsDone { get; }
        Item CurrentItem { get; }
        int Step { get; set; }
    }   

 

  • Khai báo Collection Interface và mô tả các phương thức cho tìm nạp các iterator
    //Iterable Conllection
    public interface IAbstractCollection
    {
        IAbstractIterator CreateIterator();
    }   

 

  • Triển khai Collection Interface trong các Collection class của bạn.
//Concrete Collection
    public class Collection : IAbstractCollection
    {
        List<Item> items = new List<Item>();
        public IAbstractIterator CreateIterator()
        {
            return new Iterator(this);
        }
        public int Count
        {
            get { return items.Count; }
        }
        public Item this[int index]
        {
            get { return items[index]; }
            set { items.Add(value); }
        }
    }   

 

  • Triển khai các lớp Iterator cụ thể cho các bộ collection
    Ý tưởng chính là cung cấp cho client một lối tắt để tạo các iterator, được điều chỉnh cho một Collection Class cụ thể. Đối tượng collection phải chuyển chính nó đến phương thức khởi tạo của trình lặp để thiết lập mối liên kết giữa chúng.
//Concrete Iterator
    public class Iterator : IAbstractIterator
    {
        Collection collection;
        int current = 0, step = 1;
        public Iterator(Collection collection)
        {
            this.collection = collection;
        }
        public Item First()
        {
            current = 0;
            return collection[current] as Item;
        }
        public Item Next()
        {
            current += step;
            if (!IsDone) return collection[current] as Item;
            else return null;
        }
        public int Step
        {
            get { return step; }
            set { step = value; }
        }
        public Item CurrentItem
        {
            get { return collection[current] as Item; }
        }
        public bool IsDone
        {
            get { return current >= collection.Count; }
        }
    }   

 

  • Thay thế tất cả collection bằng việc sử dụng Iterator. tại client code Máy khách tìm nạp một đối tượng iterator mới mỗi khi nó cần lặp qua collection
public class Program
    {
        public static void Main(string[] args)
        {
            // Build a collection
            Collection collection = new Collection();
            collection[0] = new Item("Item 0");
            collection[1] = new Item("Item 1");
            collection[2] = new Item("Item 2");
            collection[3] = new Item("Item 3");
            collection[4] = new Item("Item 4");
            collection[5] = new Item("Item 5");
            collection[6] = new Item("Item 6");
            collection[7] = new Item("Item 7");
            collection[8] = new Item("Item 8");
            // Create iterator
            IAbstractIterator iterator = collection.CreateIterator();
            // Skip every other item
            iterator.Step = 3;
            Console.WriteLine("Iterating over collection:");
            for (Item item = iterator.First(); !iterator.IsDone; item = iterator.Next())
            {
                Console.WriteLine(item.Name);
            }
            // Wait for user
            Console.ReadKey();
        }
    }   

V. Ưu & nhược điểm

    1. Ưu điểm

  • Đảm bảo nguyên tắc Single responsibility principle (SRP): chúng ta có thể tách phần cài đặt các phương thức của tập hợp và phần duyệt qua các phần tử (iterator) theo từng class riêng lẻ.
  • Đảm bảo nguyên tắc Open/Closed Principle (OCP): chúng ta có thể implement các loại collection mới và iterator mới, sau đó chuyển chúng vào code hiện có mà không vi phạm bất cứ nguyên tắc gì.
  • Chúng ta có thể truy cập song song trên cùng một tập hợp vì mỗi đối tượng iterator có chứa trạng thái riêng của nó.
  • Trong một vài trường hợp, bạn có thể trì hoãn một lần lặp lại và tiếp tục nó khi cần thiết.

    2. Nhược điểm

  • Sử dụng iterator có thể kém hiệu quả hơn so với việc duyệt qua các phần tử của bộ sưu tập một cách trực tiếp.
  • Có thể không cần thiết nếu ứng dụng chỉ hoạt động với các collection đơn giản.

VI. Khi nào thì sử dụng

Dưới đây chúng ta có thể liệt kê một số trường hợp mà khi gặp sẽ phải cân nhắc sử dụng Iterator pattern:

  • Cần truy cập nội dung của đối tượng trong tập hợp mà không cần biết nội dung cài đặt bên trong nó.
    Sử dụng mẫu Iterator khi collection của bạn có một cấu trúc dữ phức tạp, nhưng bạn muốn ẩn tính phức tạp chung của nó với các máy khách (để thuận tiện hoặc bảo mật) => Trình lặp đóng gói các chi tiết làm việc với cấu trúc dữ liệu phức tạp, cung cấp cho client một số phương pháp mô phỏng để truy cập các phần tử của collection.
  • Giảm sự trùng lặp của source code
    Code của các thuật toán lặp thường rất phức tạp và có xu hướng cồng kềnh => Khi được đặt trong logic kinh doanh của một ứng dụng, nó có thể làm lu mờ vai trò của source code gốc và làm cho nó khó bảo trì hơn. Với việc di chuyển source code đó đến các Iterator được chỉ định có thể giúp code của mình gọn gàng và sạch sẽ hơn.
  • Muốn có một interface duy nhất để duyệt qua các phần tử của một tập hợp, code của mình có thể follow các cấu trúc dữ liệu khác nhau hoặc khi các loại cấu trúc chuỗi này chưa được biết trước. => Mẫu cung cấp một vài Interface chung cho cả hai collection và iterator. Giúp code của bạn bây giờ sẽ sử dụng những Interface, và nó sẽ vẫn hoạt động với nhiều collection và iterator khác nhau để triển khai các giao diện này.

VII. Design Pattern liên quan

  • Composite: Iterator thường được sử dụng để duyệt một cấu trúc đệ quy như Composite
  • Factory Method: có thể kết hợp với Iterator để cho phép các lớp con của tập hợp trả về các loại trình vòng lặp khác nhau tương thích với nó
  • Memento: có thể kết hợp với Iterator để nắm bắt trạng thái lặp lại hiện tại và khôi phục nó khi cần
  • Visitor: có thể kết hợp với Iterator để xem qua một cấu trúc dữ liệu phức tạp và thực hiện một số thao tác trên các phần tử của nó.

 

Tài liệu tham khảo

[1] Refactoring.Guru

[2] Design Patterns for Dummies, Steve Holzner, PhD

[3] Head First, Eric Freeman

[4] Gang of Four Design Patterns 4.0

[5] Dive into Design Pattern

 

Chia sẽ bài viết :