【问题标题】:Ruby custom iteratorsRuby 自定义迭代器
【发布时间】:2013-09-02 18:06:37
【问题描述】:

我有一个类 game,其中包含一些自定义对象数组(恐龙、cacemen 等),这些对象由不同的访问器返回,例如 game.dinosaursgame.cavemen 等。

目前,所有这些访问器都只是返回内部存储的数组。但现在我想为这些访问器返回的这些数组添加一些自定义迭代方法,以便能够编写类似于each_elementeach_attr 中的LibXML::XML::Node 迭代器的代码,例如game.dinosaurs.each_carnivore { ... } 等。但是从我的访问器 game.dinosaursgame.cavemen 返回的对象仍然必须表现得像数组。

这样的事情通常在 Ruby 中是如何完成的? 我应该让访问器返回的对象成为一些从 Ruby 的Array 类派生的自定义类吗?或者也许我应该只创建一个带有Enumerable 的自定义类?

我知道我可以在我的集合外部使用mapselect,但我想在内部封装这些迭代,以便我的班级的用户无需费心如何设置迭代以仅从中选择食肉恐龙内部数组。

编辑:我不是在问如何使用迭代器或如何实现它们,而是如何将一些自定义迭代器添加到以前只是普通数组的对象(并且仍然需要)。

【问题讨论】:

  • 我认为Practical Object-Oriented Design in Ruby,第 8 章准确地解决了您的问题,尽管它提供了一些选项,但没有解决方案适合所有情况。您应该阅读这本书(很好!),看看哪种想法适合您的项目。

标签: ruby arrays collections each extend


【解决方案1】:

您可以通过使用红宝石块来做到这一点。 Read more

这里是一个简单的例子:

class Game

  def initialize
    @carnivoures = [1,2,3]
  end

  def each_carnivoures
    @carnivoures.each do |carni|
      yield carni
    end
  end

end

Game.new.each_carnivoures{ |c| p c}

【讨论】:

  • 对不起,我不是在问如何使用块或实现它们。我在询问将此类自定义迭代器添加到从访问器返回的对象的最佳实践是什么,该对象以前仅返回普通数组,并且大量代码依赖于它。我只是想以某种方式将那些额外的迭代方法添加到返回的数组中。我更新了我的问题,所以我希望现在更清楚了。
【解决方案2】:

这取决于(一如既往)。您可以使用数组子类,也可以构建自定义类并使用组合和委托。这是一个带有数组子类的简单示例:

class DinosaurArray < Array
  def carnivores
    select { |dinosaur| dinosaur.type == :carnivore }
  end

  def herbivores
    select { |dinosaur| dinosaur.type == :herbivore }
  end

  def each_carnivore(&block)
    carnivores.each(&block)
  end

  def each_herbivore(&block)
    herbivores.each(&block)
  end
end

这是一个简单的组合和委托:

class DinosaurArray
  def initialize
    @array = []
  end

  def <<(dinosaur)
    @array << dinosaur
  end

  def carnivores
    @array.select { |dinosaur| dinosaur.type == :carnivore }
  end

  def herbivores
    @array.select { |dinosaur| dinosaur.type == :herbivore }
  end

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

  def each_carnivore(&block)
    carnivores.each(&block)
  end

  def each_herbivore(&block)
    herbivores.each(&block)
  end
end

两种实现都可以这样使用:

require 'ostruct'

dinosaurs = DinosaurArray.new
dinosaurs << OpenStruct.new(type: :carnivore, name: "Tyrannosaurus")
dinosaurs << OpenStruct.new(type: :carnivore, name: "Allosaurus")
dinosaurs << OpenStruct.new(type: :herbivore, name: "Apatosaurus")

puts "Dinosaurs:"
dinosaurs.each.with_index(1) { |dinosaur, i| puts "#{i}. #{dinosaur.name}" }
puts

但也有自定义迭代器:

puts "Carnivores:"
dinosaurs.each_carnivore.with_index(1) { |dinosaur, i| puts "#{i}. #{dinosaur.name}" }
puts

puts "Herbivores:"
dinosaurs.each_herbivore.with_index(1) { |dinosaur, i| puts "#{i}. #{dinosaur.name}" }

输出:

Dinosaurs:
1. Tyrannosaurus
2. Allosaurus
3. Apatosaurus

Carnivores:
1. Tyrannosaurus
2. Allosaurus

Herbivores:
1. Apatosaurus

【讨论】:

  • Hmm.. 所以看起来我的猜测是我需要从Array 派生返回的对象的类是正确的。谢谢。我只是不确定这是否是最好的解决方案。
  • 视情况而定,组合和委托也是一种选择:)
  • @SasQ 添加了替代实现
【解决方案3】:

如果有可能链接这样的过滤器也很好。您可以通过将select 方法包装到自定义方法中来实现这一点,返回您的新类而不是数组。您也可以包装一些其他方法,例如map:

class Units < Array
  def select
    self.class.new(super)
  end

  def dinosaurs
    select{ |unit| unit.kind == 'dinosaur' }
  end

  def cavemen
    select{ |unit| unit.kind == 'caveman' }
  end

  def carnivore
    select{ |unit| unit.type == 'carnivore' }
  end

  def herbivore
    select{ |unit| unit.type == 'herbivore' }
  end
end

Units.dinosaurs.carnivore
Units.cavemen.herbivore

【讨论】:

    猜你喜欢
    • 2015-02-04
    • 2013-02-17
    • 2015-11-19
    • 2016-06-18
    • 2016-04-05
    • 2021-09-15
    • 2017-02-03
    • 2016-10-19
    相关资源
    最近更新 更多