Design Pattern - Iterator
Bài đăng này đã không được cập nhật trong 9 năm
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 block và Proc 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:
All rights reserved