Запах кода, IMO, в вашем коде просто в том, что это коллекция, но я не могу относиться к ней как к коллекции.Например, если бы я хотел, чтобы все учетные записи имели более 10 кредитов, мне пришлось бы извлечь массив учетных записей из коллекции, чтобы сделать это:
accounts.accounts.select { |account| account.credit > 10 }
, тогда как на самом деле вы хотитечтобы иметь возможность просто запросить у нее коллекцию:
accounts.select { |account| account.credit > 10 }
Чтобы ваша коллекция выглядела как коллекция, вам нужно включить Enumerable
, что требует реализации each
method:
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
Теперь все начинает ощущаться и пахнутьнамного лучше, с минимальнымrk и другие, пытающиеся использовать ваш AccountsCollection
, будут очень рады, что у них есть доступ ко всем методам поиска и обхода, к которым они привыкли.