在 Ruby 中使用委派维持相同的 class
Maintaining same class using delegation in Ruby
我正在努力思考委托与继承的关系,因此我手动委托了一个版本的 Array。我读到这样做的具体原因之一是因为当您使用枚举之类的东西时,继承方法上的 returned 值会恢复到父级 class(即数组)。所以我这样做了:
module PeepData
# A list of Peeps
class Peeps
include Enumerable
def initialize(list = [])
@list = list
end
def [](index)
@list[index]
end
def each(...)
@list.each(...)
end
def reverse
Peeps.new(@list.reverse)
end
def last
@list.last
end
def join(...)
@list.join(...)
end
def from_csv(csv_table)
@list = []
csv_table.each { |row| @list << Peep.new(row.to_h) }
end
def include(field, value)
Peeps.new(select { |row| row[field] == value })
end
def exclude(field, value)
Peeps.new(select { |row| row[field] != value })
end
def count_by_field(field)
result = {}
@list.each do |row|
result[row[field]] = result[row[field]].to_i + 1
end
result
end
protected
attr_reader :list
end
end
当我实例化它时,我的包含和排除函数很好,return 一个 Peeps class 但是当使用像 select 这样的可枚举时,它 returns 数组,这阻止我在 select 之后链接更多的 Peeps 特定方法。这正是我在学习委托时试图避免的。
p = Peeps.new
p.from_csv(csv_generated_array_of_hashes)
p.select(&:certified?).class
returns 数组
如果我覆盖 select,将其包装在 Peeps.new() 中,我会收到“SystemStackError:堆栈级别太深”。它似乎在 select 枚举期间递归地将列表更深地埋入列表。
def select(...)
Peeps.new(@list.select(...))
end
任何帮助和感谢!
我觉得如果Peeps#select
会return一个Array
,那么include Enumerable
就可以了。但是,你想要 Peeps#select
到 return 一个 Peeps
。我认为你不应该 include Enumerable
。如果您不符合它的界面,那么 声称 是 Enumerable
是一种误导。这只是我的看法。生态系统对此没有明确的共识。请参阅下面的“来自生态系统的示例”。
如果我们承认我们不能 include Enumerable
,这是我想到的第一个实现。
require 'minitest/autorun'
class Peeps
ARRAY_METHODS = %i[flat_map map reject select]
ELEMENT_METHODS = %i[first include? last]
def initialize(list)
@list = list
end
def inspect
@list.join(', ')
end
def method_missing(mth, *args, &block)
if ARRAY_METHODS.include?(mth)
self.class.new(@list.send(mth, *args, &block))
elsif ELEMENT_METHODS.include?(mth)
@list.send(mth, *args, &block)
else
super
end
end
end
class PeepsTest < Minitest::Test
def test_first
assert_equal('alice', Peeps.new(%w[alice bob charlie]).first)
end
def test_include?
assert Peeps.new(%w[alice bob charlie]).include?('bob')
end
def test_select
peeps = Peeps.new(%w[alice bob charlie]).select { |i| i < 'c' }
assert_instance_of(Peeps, peeps)
assert_equal('alice, bob', peeps.inspect)
end
end
我平时不怎么用method_missing
,不过好像很方便。
来自生态系统的示例
关于如何严格遵循接口似乎没有达成共识。
ActionController::Parameters
用于继承Hash
。继承在 Rails 5.1. 中停止
ActiveSupport::HashWithIndifferentAccess
仍然继承Hash
.
如另一个答案中所述,这并不是 Enumerable
的正确用法。也就是说,您仍然可以包含 Enumerable
并使用一些 meta-programming 来覆盖您想要的方法 peep-chainable:
module PeepData
class Peeps
include Enumerable
PEEP_CHAINABLES = [:map, :select]
PEEP_CHAINABLES.each do |method_name|
define_method(method_name) do |&block|
self.class.new(super(&block))
end
end
# solution for select without meta-programming looks like this:
# def select
# Peeps.new(super)
# end
end
end
如你所知,这与继承与委派无关。如果 Peeps
扩展 Array
,您将遇到完全相同的问题,并且上面的确切解决方案仍然有效。
我建议使用您可能想要包含的两种 Forwardable and Enumerable. Use Forwardable to delegate the each
method to your list (to satisfy the Enumerable interface requirement), and also forward any Array 方法,它们不是 Enumerable 模块的一部分,例如 size
。我还建议 不要 覆盖 select
的行为,因为它应该 return 一个数组并且至少会导致混淆。我建议使用下面提供的类似 subset
的方法来实现您正在寻找的行为。
require 'forwardable'
class Peeps
include Enumerable
extend Forwardable
def_delegators :@list, :each, :size
def initialize(list = [])
@list = list
end
def subset(&block)
selected = @list.select(&block)
Peeps.new(selected)
end
protected
attr_reader :list
end
用法示例:
peeps = Peeps.new([:a,:b,:c])
subset = peeps.subset {|s| s != :b}
puts subset.class
peeps.each do |peep|
puts peep
end
puts peeps.size
puts subset.size
产生:
Peeps
a
b
c
3
2
我正在努力思考委托与继承的关系,因此我手动委托了一个版本的 Array。我读到这样做的具体原因之一是因为当您使用枚举之类的东西时,继承方法上的 returned 值会恢复到父级 class(即数组)。所以我这样做了:
module PeepData
# A list of Peeps
class Peeps
include Enumerable
def initialize(list = [])
@list = list
end
def [](index)
@list[index]
end
def each(...)
@list.each(...)
end
def reverse
Peeps.new(@list.reverse)
end
def last
@list.last
end
def join(...)
@list.join(...)
end
def from_csv(csv_table)
@list = []
csv_table.each { |row| @list << Peep.new(row.to_h) }
end
def include(field, value)
Peeps.new(select { |row| row[field] == value })
end
def exclude(field, value)
Peeps.new(select { |row| row[field] != value })
end
def count_by_field(field)
result = {}
@list.each do |row|
result[row[field]] = result[row[field]].to_i + 1
end
result
end
protected
attr_reader :list
end
end
当我实例化它时,我的包含和排除函数很好,return 一个 Peeps class 但是当使用像 select 这样的可枚举时,它 returns 数组,这阻止我在 select 之后链接更多的 Peeps 特定方法。这正是我在学习委托时试图避免的。
p = Peeps.new
p.from_csv(csv_generated_array_of_hashes)
p.select(&:certified?).class
returns 数组
如果我覆盖 select,将其包装在 Peeps.new() 中,我会收到“SystemStackError:堆栈级别太深”。它似乎在 select 枚举期间递归地将列表更深地埋入列表。
def select(...)
Peeps.new(@list.select(...))
end
任何帮助和感谢!
我觉得如果Peeps#select
会return一个Array
,那么include Enumerable
就可以了。但是,你想要 Peeps#select
到 return 一个 Peeps
。我认为你不应该 include Enumerable
。如果您不符合它的界面,那么 声称 是 Enumerable
是一种误导。这只是我的看法。生态系统对此没有明确的共识。请参阅下面的“来自生态系统的示例”。
如果我们承认我们不能 include Enumerable
,这是我想到的第一个实现。
require 'minitest/autorun'
class Peeps
ARRAY_METHODS = %i[flat_map map reject select]
ELEMENT_METHODS = %i[first include? last]
def initialize(list)
@list = list
end
def inspect
@list.join(', ')
end
def method_missing(mth, *args, &block)
if ARRAY_METHODS.include?(mth)
self.class.new(@list.send(mth, *args, &block))
elsif ELEMENT_METHODS.include?(mth)
@list.send(mth, *args, &block)
else
super
end
end
end
class PeepsTest < Minitest::Test
def test_first
assert_equal('alice', Peeps.new(%w[alice bob charlie]).first)
end
def test_include?
assert Peeps.new(%w[alice bob charlie]).include?('bob')
end
def test_select
peeps = Peeps.new(%w[alice bob charlie]).select { |i| i < 'c' }
assert_instance_of(Peeps, peeps)
assert_equal('alice, bob', peeps.inspect)
end
end
我平时不怎么用method_missing
,不过好像很方便。
来自生态系统的示例
关于如何严格遵循接口似乎没有达成共识。
ActionController::Parameters
用于继承Hash
。继承在 Rails 5.1. 中停止
ActiveSupport::HashWithIndifferentAccess
仍然继承Hash
.
如另一个答案中所述,这并不是 Enumerable
的正确用法。也就是说,您仍然可以包含 Enumerable
并使用一些 meta-programming 来覆盖您想要的方法 peep-chainable:
module PeepData
class Peeps
include Enumerable
PEEP_CHAINABLES = [:map, :select]
PEEP_CHAINABLES.each do |method_name|
define_method(method_name) do |&block|
self.class.new(super(&block))
end
end
# solution for select without meta-programming looks like this:
# def select
# Peeps.new(super)
# end
end
end
如你所知,这与继承与委派无关。如果 Peeps
扩展 Array
,您将遇到完全相同的问题,并且上面的确切解决方案仍然有效。
我建议使用您可能想要包含的两种 Forwardable and Enumerable. Use Forwardable to delegate the each
method to your list (to satisfy the Enumerable interface requirement), and also forward any Array 方法,它们不是 Enumerable 模块的一部分,例如 size
。我还建议 不要 覆盖 select
的行为,因为它应该 return 一个数组并且至少会导致混淆。我建议使用下面提供的类似 subset
的方法来实现您正在寻找的行为。
require 'forwardable'
class Peeps
include Enumerable
extend Forwardable
def_delegators :@list, :each, :size
def initialize(list = [])
@list = list
end
def subset(&block)
selected = @list.select(&block)
Peeps.new(selected)
end
protected
attr_reader :list
end
用法示例:
peeps = Peeps.new([:a,:b,:c])
subset = peeps.subset {|s| s != :b}
puts subset.class
peeps.each do |peep|
puts peep
end
puts peeps.size
puts subset.size
产生:
Peeps
a
b
c
3
2