我正在构建一个LineItemGenerator
对象,其目的是在给定所需属性的情况下生成属性值数组。
问题是给定的对象将对象作为属性。所以"属性"给定的是#34;嵌套属性"。
目标是通过创建一些输入数据结构并使用某种算法,从item
访问所请求的嵌套属性,在本例中为item.name
和item.style.name
。
目前,我代表我的"嵌套属性"将数据结构输入为数组数组nested_attributes
我执行繁重任务的算法称为#generate
。
需要原始item
和nested_attributes
。接下来,它映射到nested_attributes
,将每个nested_attribute
缩减为"属性"通过send
在每次迭代时将消息发送到原始item
。
class Style
attr_reader :name
def initialize name:
@name = name
end
end
class Item
attr_reader :name, :style
def initialize name:, style:
@name = name
@style = style
end
end
class LineItemGenerator
def generate item:, nested_attributes:
nested_attributes.map do |nested_attribute|
nested_attribute.reduce(item) do |obj, attribute| # <-- algorithm using #reduce to burrow in
obj.send(attribute)
end
end
end
end
require 'minitest/autorun'
class SomeTest < Minitest::Test
def test_it_returns_the_right_line_item
style = Style.new name: 'cool'
item = Item.new name: 'pants', style: style
# input data structure is array or arrays
nested_attributes = [[:name], [:style, :name]]
input = { item: item, nested_attributes: nested_attributes}
output = LineItemGenerator.new.generate input
assert_equal ['pants', 'cool'], output
end
end
我很好奇实现我的输入数据结构和算法的新方法更具说明性和表现力。感兴趣的两个部分都在上面的评论中提到。
使用#inject
感觉很奇怪,因为我真的只是试图将可变数量的send
次呼叫链接在一起。例如:
item = Item.new name: 'pants', style: Style.new(name: 'cool')
p item.send(:style).send(:name) #=> "cool"
在这种情况下,是否有一些Enumerable方法是更好的选择?我的输入数据结构有更好的选择吗?
答案 0 :(得分:1)
这对我来说更像是一个软件设计问题,所以这就是我从设计角度来看待它的方法。
LineItemGenerator
的工作是为给定所需属性的项目生成属性值数组。
基于此,LineItemGenerator
:
generate_attribute_values
这可能如下所示:
LineItemGenerator.new(@item).generate_attribute_values(:name, :style)
我删除generate
,因为它似乎不是正确的词。我们只是检索和过滤现有值,而不是创建新的属性值对象。
LineItemGenerator.new(@item).attribute_values(:name, :style)
此时,我会考虑Item
应该向LineItemGenerator
公开的内容。
鉴于这种理解,我可以将LineItemGenerator
实现为:
class LineItemGenerator
def initialize(item)
@item = item
end
def attribute_values(*attribute_names)
@item.attributes.select { |attribute| attribute_names.include?(attribute.name) }.map(&:value)
end
end
此时,有两个合同需要履行:
#attributes
item.attributes
需要返回一组响应#name
和#value
现在,让我们从一个项目的角度思考。
- 项目具有许多属性(例如,名称和样式)。
- 可以在Item
对象上定义相关属性值,也可以将其委托给其他对象。
合同1很容易实现:
class Item
attr_reader :attributes
end
合同2更灵活,因为它可以在Item或单个属性类上实现。如果属性不是应用程序中的第一类关注点,我会在Item
上实现它。
class Item
attr_reader :attributes
Attribute = Struct.new(:name, :value)
def initialize(name:, style:)
@attributes = [
Attribute.new(name: :name, value: name),
Attribute.new(name: :style, value: style)
]
end
end
如果系统的某些其他部分需要与属性作为第一类关注点进行交互:
# TODO: DRY up using inheritance or modules
class Style
attr_reader :value
def initialize value:
@value = value
end
def name
:style
end
end
class ItemName
attr_reader :value
def initialize value:
@value = value
end
def name
:name
end
end
class Item
attr_reader :name, :style, :attributes
def initialize item_name:, style:
@name = item_name
@style = style
@attributes = [@name, @style]
end
end