The Iterator Pattern trong Ruby

Mình có ví dụ như này:

User.all.each do |u|
      puts u.name
end

Kết quả của khối lệnh này bạn sẽ thấy

Nguyen Van A
Nguyen Thi B

Bạn code Ruby, bạn đã bao giờ tự hỏi: tại sao khi sử dụng vòng lặp each do mà bạn có thể chạy từ đầu mảng đến cuối mảng chưa? Câu trả lời là: Trong khi định nghĩa hàm each do , Ruby đã setup sẵn việc lặp cho bạn rồi! Vậy nếu không dùng hàm này, bạn có thể dùng cách nào để in ra như vậy? "Dùng Iterator" đó là cách mình nghĩ đến! Vậy Iterator là gì?

1. Iterator là gì?

Iteractor cung cấp một cách để truy cập vào dữ những phần tử của một tập các đối tượng tuần tự mà không làm lộ cách thể hiện (VD kiểu dữ liệu) của chúng. Hay có thể nói rằng, Iteractor cung cấp cho bạn một phương pháp, 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.

2. UML class diagram

3. Participants

Các lớp và các đối tượng tham gia trong mô hình này là:

Iterator (AbstractIterator): định nghĩa một interface để truy cập và đi qua các phần tử. ConcreteIterator (Iterator): implements interface Iterator, theo dõi vị trí hiện tại trong vòng lặp aggregate Aggregate (AbstractCollection): định nghĩa một interface để tạo một đối tượng Iterator ConcreteAggregate (Collection): implements phương thức tạo interface Iterator để trả về return biến instance của ConcreteIterator riêng

4.Ví dụ

Ví dụ về ứng dụng của Iteractor Pattern bạn có thể nhìn rõ nhất trong Enumerable của Ruby.

Do vậy khi thực hiện các bạn đều có thể dùng như sau:

class Account
  attr_accessor :name, :balance

  def initialize(name, balance)
    @name = name
    @balance = balance
  end

  def <=>(other)
    balance <=> other.balance
  end
end
class Portfolio
  include Enumerable

  def initialize
    @accounts = []
  end

  def each(&block)
    @accounts.each(&block)
  end

  def add_account(account)
    @accounts << account
  end
end

Bây giờ, Iterator có thể cho bạn một cách để có thể chạy qua của hai class là Portfolio và Account. Nhìn xem cách ta sử dụng nhé!

my_portfolio =  Portfolio.new
my_portfolio.add_account(Account.new('Bonds', 200))
my_portfolio.add_account(Account.new('Stocks', 100))
my_portfolio.add_account(Account.new('Real Estate', 1000))

my_portfolio.any? { |account| account.balance > 2000 }
my_portfolio.all? { |account| account.balance >= 10 }
my_portfolio.each { |account| puts "#{account.name}: #{account.balance}" }
my_portfolio.map { |account| account.balance }
my_portfolio.max
my_portfolio.min

Các bạn hãy chạy thử nhé!

5.Thêm về Iterators

5.1. External Iterator

Logic của Iterator chứa các class riêng biệt. Class interation này có thể tổng quát hóa để handle nhiều kiểu đối tuonwjg miễn là chúng cho phép đánh chỉ mục (index) External Iterator yêu cầu các lớp bổ sung để thực hiện việc lặp, nhưng chúng cực kì linh hoạt bởi vì bạn có thể control vòng lặp như thế nào, các phần tử nào được lặp lại và lặp theo thứ tự nào.

5.2. Internal Iterator

Tất cả việc xử lý của vòng lặp xảy ra ở bên trong đối tượng của Aggregate. Sử dụng code block để pass qua việc xử lý logic của aggregate sau đó gọi block với mỗi elements Ví dụ:

colors = ['red', 'green', 'blue']
colors.each { |color| puts color }

5.3. Lỗi

Đôi khi, với việc tùy chỉnh các iterator, nếu lớp collection gốc thay đổi trong khi bạn lặp lại thông qua các phần tử của nó, nó có thể tạo ra kết quả không mong muốn. Để khắc phục điều này, bạn có thể có các Iterator hoạt động trên một bản sao của bộ collection ban đầu.

class ArrayIterator
  def initialize(array)
  	@array = Array.new(array)
  	@index = 0
  end
  …

6. Tài liệu tham khảo

https://github.com/nslocum/design-patterns-in-ruby#iterator http://index-of.es/Ruby/Design Patterns in Ruby (2007).pdf


All Rights Reserved