Design Pattern - Iterator

Iterator dùng để làm gì?

Ở phần trước, chúng ta đã tìm hiểu về Design Pattern Composite. Nó là kỹ thuật được thiết kế cho phép bạn xử lý nhiều đối tượng khác chủng loại trong cùng một tập hợp theo cùng một cách.

Điều đặc biệt là Pattern này có quan hệ mật thiết với 1 design pattern cũng liên quan tới xử lý tập hợp mà chúng ta sẽ đề cập tới trong bài viết này, đó là Design Pattern Iterator

Đây là một kỹ thuật cho phép truy cập vào các tập đối tượng con trong một tập hợp đối tượng lớn hơn.

Theo GoF định nghĩa thì ta có ý nghĩa của Iterator như sau:

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation

Có thể dịch như thế này: nó cung cấp một cách để truy cập những phần tử của một tập các đối tượng một cách tuần tự mà không làm lộ các cách thức thể hiện của chúng.

Cũng có thể hiểu là: Iterator được thiết kế để giúp bạn 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 theo 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.

Cài đặt trong Ruby

Thử xem 1 đoạn code sau:

class ArrayIterator
  def initialize(array)
    @array = array
    @index = 0
  end
  def has_next?
    @index < @array.length
  end
  def item
    @array[@index]
  end
  def next_item
    value = @array[@index]
    @index += 1
    value
  end
end

External Iterators

array = ['red', 'green', 'blue']
i = ArrayIterator.new(array)
while i.has_next?
  puts("item: #{i.next_item}")
end

Kết quả của code trên là:

item: red
item: green
item: blue

Việc sử dụng Iterator như trên còn được gọi là External Iterators vì các phần tử được duyệt tuần tự bên ngoài class Iterators

Ta có thể thấy là việc truyền vào tập (array) là string hay character thì ta vẫn có thể truy cập và duyệt các phần tử con một cách dễ dàng.

Tuy nhiên, nếu ta chạy code sau, class ArrayIterator sẽ vẫn chạy đúng:

i = ArrayIterator.new('abc')
while i.has_next?
  puts("item: #{i.next_item.chr}")
end

Kết quả:

item: a
item: b
item: c

Tuy nhiên ở đây ta thấy với mỗi kiểu dữ liệu của các thành phần thì việc hiển thị giữa string và character hơi khác một chút, đó là cần thêm .chr để có thể hiện thị phần tử charactor. Nhưng việc này cũng khá dễ dàng đối với Ruby vì chúng ta có thể sử dụng code blockProc object (nếu bạn đã làm quen với 2 khái niệm này trong Ruby).

Internal Iterators

Để giải quyết cho vấn đề trên ta sử dụng Internal Iterators (vòng lặp trong) Việc xây dựng Internal Iterators rất đơn giản, chúng ta chỉ cần định nghĩa một method gọi một code block thông qua yield cho mỗi phần tử như sau:

def for_each_element(array)
  i = 0
  while i < array.length
    yield(array[i])
    i += 1
  end
end

Ta gọi method và truyền vào code block:

a = [10, 20, 30]
for_each_element(a) {|element| puts("The element is #{element}")}

Kết quả ta có:

The element is 10
The element is 20
The element is 30

So sánh Internal && External Iterators

Mỗi cách thức để có những lợi thế riêng.

Với External Iterators, chúng ta phải tự điều chỉnh với mỗi vòng lặp. Còn với Internal Iterators, nó sẽ tự động làm việc khi block code được truyền vào.

Sự khác biệt này không có thật sự quan trọng vì hai cách thức là gần tương đương nhau. Tuy nhiên xét về mặt thực tiễn, thì External Iterators sẽ có lợi thế hơn. Bởi có nhiều trường hợp chúng ta dùng External Iterators sẽ dễ dàng hơn Internal Iterators. Nó giống như việc nếu chúng ta muốn kết hợp hai mảng và sắp xếp chúng lại theo thứ tự.

Nếu sử dụng External Iterators sẽ rất dễ dàng vì chúng ta có thể duyệt phần tử của hai mảng từ bên ngoài và so sánh chúng với nhau. Còn nếu sử dụng Internal Iterators, các phần tử được duyệt bên trong do đó ta không thể so sánh nó với các phần tử của mảng khác được, chúng ta chỉ còn cách merge hai mảng với nhau sau đó duyệt từng phần tử của mảng mới. Rõ ràng việc dùng External Iterators đem lại performance cao hơn.

Cài đặt việc merge hai mảng với External Iterators:

def merge(array1, array2)
  merged = []
  iterator1 = ArrayIterator.new(array1)
  iterator2 = ArrayIterator.new(array2)
  while( iterator1.has_next? and iterator2.has_next? )
    if iterator1.item < iterator2.item
      merged << iterator1.next_item
    else
      merged << iterator2.next_item
    end
  end
  # Pick up the leftovers from array1
  while( iterator1.has_next?)
    merged << iterator1.next_item
  end
  # Pick up the leftovers from array2
  while( iterator2.has_next?)
    merged << iterator2.next_item
  end
  merged
end

Tham khảo

Github (updating):https://github.com/ducnhat1989/design-patterns-in-ruby

Sách: “DESIGN PATTERNS IN RUBY” của tác giả Russ Olsen

Bài viết liên quan: