Bí ẩn của vòng lặp

Chắc chúng ta ai cũng đã từng làm việc với vòng foreach rồi nhỉ? Nó cũng giống như ăn cơm uống nước hàng ngày vậy, nó kiểu thế này:

for (Object item : items) {
    // làm gì đó với item
}

Nhưng liệu cơ chế hoạt động ở bên trong có giống như chúng ta nghĩ? Chúng ta sẽ cùng đi giải đáp bí ẩn này nhé.

Bài toán thực tế

Ở mức cơ bản chúng ta sẽ có các kiểu vòng lặp sau:

  1. Lặp theo index
for (int i = 0 ; i < n ; ++i) {
    // làm gì đó
}
  1. Lặp theo while
while (condition) {
    // làm gì đó
}

Bây giờ chúng ta sẽ có một lớp kiểu này:

public class MyCollection<T> {

    private int count = 0;
    private Object[] items = new Object[100];
}

Câu hỏi đặt ra là làm thế nào chúng ta có thể lặp qua tất cả tất cả các items khi nó được khai báo là private?

Mục tiêu ra đời

Qua ví dụ ở trên chúng ta có thể thấy được iterator design pattern nhằm mục tiêu:

  • Cung cấp một cơ chế để lặp qua tất cả các phần tử hay thuộc tính của một đối tượng.
  • Cho phép che giấu các thành phần trong đối tượng từ đó đảm bảo tính đóng gói. Ví dụ ở trên chúng ta sẽ không cần phải public thuộc tính items của lớp MyCollection.

Mô hình tiêu chuẩn

Iterator sẽ bao gồm 4 thành phần cơ bản:

  1. Iterator: chứa 2 hàm hashNext để kiểm tra xem còn phần tử nào tiếp theo không và next để lấy phần tử kế tiếp
  2. ConcreteIterator: là lớp cài đặt Iterator, một ví dụ điển hình là lớp ArrayList.Itr
  3. Aggregate: Là interface sẽ cần sử dụng Iterator để lặp qua các phần tử cho lớp cài đặt, điển hình là là lớp Iterable
  4. ConcreteAggregate: Là lớp cài đặt Aggregate, lớp này sẽ chứa các phần tử hoặc thuộc tính cần sử dụng Iterator để lặp qua

Ví dụ

Bây giờ, chúng ta hãy cùng quay lại bài toàn với MyCollection ở trên nhé. Đầu tiên, hãy thiết kế lớp một chút.

Sơ đồ lớp

Sơ đồ lớp cúng sẽ tuân theo mô hình tiêu chuẩn ở trên, tuy nhiên chúng ta sẽ sử dụng các đối tượng mặc định của java là IteratorIterable đã có sẵn.

Cài đặt

Việc cài đặt cũng tương đối đơn giản, bạn có thể tham khảo bản đầy đủ tại đây:

public class MyCollection<T> implements Iterable<T> {

    private int size = 0;
    private Object[] items = new Object[100];

    public void add(T item) {
        this.items[size ++] = item;
    }

    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }

    class MyIterator implements Iterator<T> {

        private int index;

        @Override
        public boolean hasNext() {
            return index < size;
        }

        @SuppressWarnings("unchecked")
        @Override
        public T next() {
            return (T)items[index ++];
        }
    }
}

Và khi sử dụng sẽ thế này:

MyCollection<String> collection = new MyCollection<>();
collection.add("a");
collection.add("b");
collection.add("c");
for (String item : collection) {
    System.out.print(item + ", ");
}

Như bạn có thể thấy, việc sử dụng cũng tương đối đơn giản, java thậm chí còn cho phép chúng ta sử dụng for each với iterator luôn, nó tương đối tiện.

Tổng kết

Iterator là một design rất thông dụng, tuy nhiên chúng ta cũng ít khi phải quan tâm khi cách thư viện và ngôn ngữ lập trình đã wrap lại cho chúng ta rồi. Tuy nhiên, việc biết thêm Iterator sẽ giúp chúng ta có thêm nhiều lựa chọn trong trường hợp cần thiết.