我正試圖圍繞委托與繼承展開思考,所以我手動委托了一個 Array 版本。我讀到這樣做的具體原因之一是因為當您使用諸如列舉之類的東西時,您在繼承方法上的回傳值會恢復為父類(即陣列)。所以我這樣做了:
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
當我實體化它時,我的 include 和 exclude 函式很好并回傳一個 Peeps 類,但是當使用像 select 這樣的列舉時,它回傳 Array,這阻止了我在 select 之后鏈接進一步的 Peeps 特定方法。這正是我在學習委派時試圖避免的。
p = Peeps.new
p.from_csv(csv_generated_array_of_hashes)
p.select(&:certified?).class
回傳陣列
如果我覆寫選擇,將其包裝在 Peeps.new() 中,我會得到一個“SystemStackError:堆疊級別太深”。在選擇列舉期間,它似乎遞回地將串列埋入串列中。
def select(...)
Peeps.new(@list.select(...))
end
任何幫助,謝謝!
uj5u.com熱心網友回復:
我建議同時使用Forwardable和Enumerable。使用 Forwardable 將each方法委托給您的串列(以滿足 Enumerable 介面要求),并轉發您可能想要包含的不屬于 Enumerable 模塊的任何Array方法,例如size. 我還建議不要覆寫它的行為,select因為它應該回傳一個陣列,至少會導致混亂。我會建議像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
uj5u.com熱心網友回復:
我認為如果Peeps#select會回傳一個Array,那么它是可以的include Enumerable。但是,你想Peeps#select回傳一個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。
uj5u.com熱心網友回復:
正如另一個答案中提到的,這并不是真正正確使用Enumerable. 也就是說,您仍然可以包含Enumerable并使用一些元編程來覆寫您希望可窺視的方法:
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
正如您所知,這實際上與繼承與委托無關。如果Peepsextended Array,您將遇到完全相同的問題,并且上面的確切解決方案仍然有效。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/459972.html
