存储 Ruby 对象的集合



我正在尝试将对象的集合存储在另一个对象中。我完成了如下所示的编码挑战。

首先是奇异对象:

class Account
attr_reader :user_name, :credit, :debit
def initialize(user_name)
@user_name = user_name
@credit = 0
@debit = 0
end
end

接下来是集合:

class AccountsCollection
attr_reader :accounts
def initialize
@accounts = []
end
def add_new_account(user)
accounts << Account.new(user)
end
...
end

这是我使用它的方式:

accounts = AccountsCollection.new
# => #<AccountsCollection:0x00007fc76ba70b18 @accounts=[]>
accounts.add_new_account('A')
accounts.add_new_account('B')
accounts.add_new_account('C')
accounts.accounts
# =>[
#     #<Account:0x00007fc76b933890 @user_name="A">,
#     #<Account:0x00007fc76bc76d68 @user_name="B">,
#     #<Account:0x00007fc76c88c2d8 @user_name="C">
#   ]

我想像这样使用它:

class Display
attr_reader :accounts
def initialize(accounts)
@accounts = accounts
end
def display_inline
accounts.each do |account|
#do something
end
...
end
Display.new(accounts.accounts).display_inline

但我必须调用accounts.accounts才能获取帐户对象列表。这很奇怪吗?谁能告诉我如何做得更好?

真的,除了命名看起来很尴尬之外,它对我来说看起来不错。如果是我,我会有这样的名字,所以使用它看起来不错。

class AccountsCollection
def initialize
@accounts = []
end
def add(user)
accounts << Account.new(user)
end
def to_a
@accounts
end
end

然后你的代码看起来像

accounts = AccountsCollection.new
=> #<AccountsCollection:0x00007fc76ba70b18 @accounts=[]>
accounts.add('A')
accounts.add('B')
accounts.add('C')
accounts.to_a
=>[
#<Account:0x00007fc76b933890 @user_name="A">,
#<Account:0x00007fc76bc76d68 @user_name="B">,
#<Account:0x00007fc76c88c2d8 @user_name="C">
]

IMO,代码的味道,与你的代码只是它是一个集合,但我不能把它当作一个集合。例如,如果我想要所有拥有超过 10 个积分的帐户,我必须从集合中提取帐户数组才能执行此操作:

accounts.accounts.select { |account| account.credit > 10 }

而您真正想要的是能够查询集合:

accounts.select { |account| account.credit > 10 }

若要使集合感觉更像集合,需要包含Enumerable这需要实现each方法:

class AccountsCollection
include Enumerable
def initialize
@accounts = []
end
def add(user_name)
@accounts << Account.new(user_name)
self # return self because that's more in-line with other collections
end
def each
return to_enum(__method__) { @accounts.size } unless block_given?
@accounts.each { |account| yield account }
end
end

有了这个,您现在可以像对待 Ruby 中的其他系列一样对待您的收藏:

accounts = AccountsCollection.new
accounts.add('A').add('B').add('C')
accounts.each.with_index do |account, index|
# I changed the attr_reader in Account to attr_accessor just to illustrate
account.credit = index * 10
end
accounts.select { |account| account.credit > 10 }
# => [#<Account:0x00007fe50d894208 @user_name="C", @credit=20, @debit=0>]

如果你想把它作为一个数组,你也会自动得到一个to_a方法:

accounts.to_a
# => [#<Account:0x00007f879a094538 @user_name="A", @credit=0, @debit=0>,
#     #<Account:0x00007f879a094308 @user_name="B", @credit=10, @debit=0>,
#     #<Account:0x00007f879a0968b0 @user_name="C", @credit=20, @debit=0>]

这(默认情况下(将通过each方法创建一个新数组,因此数组不会被直接修改(尽管内容仍然可以修改(

accounts.to_a[1].debit = 15
accounts.to_a << 'not an account'
accounts.to_a
# => [#<Account:0x00007fb51c08a9c0 @user_name="A", @credit=0, @debit=0>,
#     #<Account:0x00007fb51c08a6a0 @user_name="B", @credit=10, @debit=15>,
#     #<Account:0x00007fb51c08a588 @user_name="C", @credit=20, @debit=0>]

如果需要,您可以随时将任何Enumerable方法替换为更好的自定义实现:

def to_a
@accounts.dup
end

现在,事情开始感觉和闻起来好多了,只需最少的工作,其他尝试使用您的AccountsCollection的人会非常高兴他们可以使用他们习惯的所有搜索和遍历方法。

最新更新