class Collection
def self.parse(text)
songs = text.lines.each_slice(4).map do |name, artist, album|
Song.new name.chomp, artist.chomp, album.chomp
end
new songs
end
end
Ще обърнем внимание на няколко неща
Може да го запишем по-кратко, но предпочитаме предния запис, защото е по-разбираем
text.lines.each_slice(4).map do |lines|
Song.new(*lines.take(3).map(&:chomp))
end
Song.new name.chomp, artist.chomp, album.chomp
Обърнете внимание:
chomp
-а е извън Song#initialize
class Song
attr_reader :name, :artist, :album
def initialize(name, artist, album)
@name = name
@artist = artist
@album = album
end
end
Нищо драматично:
class Collection
def initialize(songs)
@songs = songs
end
end
class Collection
include Enumerable
def each(&block)
@songs.each(&block)
end
end
class Collection
def artists
@songs.map(&:artist).uniq
end
def names
@songs.map(&:name).uniq
end
def albums
@songs.map(&:album).uniq
end
end
class Collection
def artists
collect_unique :artist
end
private
def collect_unique(attribute)
@songs.map(&attribute).uniq
end
end
private
@songs
(частен интерфейс), а не songs
(публичен)class Collection
def filter(criteria)
Collection.new @songs.select { |song| criteria.met_by? song }
end
end
Обърнете внимание:
Collection
, а не друг клас
@songs.select
вместо self.select
criteria.met_by?(song)
вместо song.meets?(criteria)
.class Collection
def adjoin(other)
adjoined_songs = (songs + other.songs).uniq
Collection.new adjoined_songs
end
end
==
и eql?
проверяват идентитет на обекти
Collection.parse
- няма значениеclass Song
def ==(other)
name == other.name and
artist == other.artist and
album == other.album
end
alias eql? ==
def hash
[@name, @other, @album].hash
end
end
eql?
, трябва да предефинираме и hash
Array#uniq
hash
@songs.select { |song| criteria.met_by? song }
Criteria.artist
module Criteria
def self.name(name)
NameMatches.new name
end
class NameMatches < Criterion
def initialize(name)
@name = name
end
def met_by?(song)
song.name == @name
end
end
end
class ArtistMatches < Criterion
def initialize(artist)
@artist = artist
end
def met_by?(song)
song.artist == @artist
end
end
class AlbumMatches < Criterion
def initialize(album)
@album = album
end
def met_by?(song)
song.album == @album
end
end
Criterion
, а не Criteria
class Criterion
def &(other)
Conjunction.new self, other
end
def |(other)
Disjunction.new self, other
end
def !
Negation.new self
end
end
class Conjunction < Criterion
def initialize(left, right)
@left = left
@right = right
end
def met_by?(song)
@left.met_by?(song) and @right.met_by?(song)
end
end
class Disjunction < Criterion
def initialize(left, right)
@left = left
@right = right
end
def met_by?(song)
@left.met_by?(song) or @right.met_by?(song)
end
end
class Negation < Criterion
def initialize(criterion)
@criterion = criterion
end
def met_by?(song)
not @criterion.met_by?(song)
end
end
def Criteria.artist(artist)
AttributeMatches.new :artist, artist
end
class AttributeMatches
def initialize(attribute, value)
@attribute = attribute
@value = value
end
def met_by?(song)
song.send(@attribute) == value
end
end
module Criteria
def self.artist(artist)
Criterion.new { |song| song.artist == artist }
end
end
class Criterion
def initialize(&block)
@matcher = block
end
def met_by?(song)
@matcher.call song
end
end
class Criterion
def &(other)
Criterion.new { |song| self.met_by?(song) and other.met_by?(song) }
end
def |(other)
Criterion.new { |song| self.met_by?(song) or other.met_by?(song) }
end
def !
Criterion.new { |song| not met_by?(song) }
end
end