Паттерны проектирования в Ruby: Итератор (Iterator)

8 ноября 2015, воскресенье

Еще один поведенческий шаблон проектирования — Итератор. Представляет собой объект, позволяющий получить последовательный доступ к элементам объекта-агрегата без использования описаний каждого из агрегированных объектов. Перебор элементов выполняется объектом итератора, а не самой коллекцией. Это упрощает интерфейс и реализацию коллекции, а также способствует более логичному разделению обязанностей. Рассмотрим этот шаблона на примере инвентаризации героя в игре.

Создадим класс Inventory, который будет содержать список неких Item'ов.

class Inventory

  attr_reader :items

  def initialize
    @items = []
  end

  def add(item)
    @items << item
  end

end

class Item

  attr_reader :title, :cost

  def initialize(title, cost=0)
    @title = title
    @cost = cost
  end

end

И теперь создадим класс InventoryIterator, который будет работать с элементам объекта-агрегата класса Inventory.

class InventoryIterator

  def initialize(inventory)
    @items = inventory.items
    @index = 0
  end

  def has_next?
    @index < @item.size
  end

  def next
    value = @items[@index]
    @index+=1
    value
  end

end

Проверим работу

item0 = Item.new('АК-47', 2700)
item1 = Item.new('Helmet', 650)
item2 = Item.new('Deagle', 800)

inventory = Inventory.new
inventory.add(item0)
inventory.add(item1)
inventory.add(item2)

iterator = InventoryIterator.new(inventory)
p iterator.next.title #'АК-47'
p iterator.next.title #'Helmet'
p iterator.next.title #'Deagle'

Это был пример так называемого «внешнего итератора», теперь добавим еще и внутренний. Для этого нужно заинклудить модуль Enumerable в класс Inventory и добавить метод each:

class Inventory

  include Enumerable
  attr_reader :items

  def initialize
    @items = []
  end

  def add(item)
    @items << item
  end

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

end

А в класс Item добавим правило сравнения объектов по цене:

class Item

  attr_reader :title, :cost

  def initialize(title, cost=0)
    @title = title
    @cost = cost
  end

  def <=>(other)
    @cost <=> other.cost
  end

end

Теперь попытаемся определить самый доргой и самый дешевый Item в коллекции:

item0 = Item.new('АК-47', 2700)
item1 = Item.new('Helmet', 650)
item2 = Item.new('Deagle', 800)

inventory = Inventory.new
inventory.add(item0)
inventory.add(item1)
inventory.add(item2)

iterator = InventoryIterator.new(inventory)

p inventory.max.title #'АК-47'
p inventory.min.title #'Helmet'

Также в качестве примера реализации итераторов можно привести объект класса IO

#внешний итератор
f = File.open('names.txt')
while not f.eof?
  puts(f.readline)
end
f.close

#внутренний итератор
f = File.open('names.txt')
f.each {|line| puts(line)}
f.close