访问ruby对象

时间:2017-02-04 22:07:48

标签: ruby oop object-oriented-analysis

我正在构建一个LineItemGenerator对象,其目的是在给定所需属性的情况下生成属性值数组。

问题是给定的对象将对象作为属性。所以"属性"给定的是#34;嵌套属性"。

目标是通过创建一些输入数据结构并使用某种算法,从item访问所请求的嵌套属性,在本例中为item.nameitem.style.name

目前,我代表我的"嵌套属性"将数据结构输入为数组数组nested_attributes

我执行繁重任务的算法称为#generate

需要原始itemnested_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方法是更好的选择?我的输入数据结构有更好的选择吗?

1 个答案:

答案 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

此时,有两个合同需要履行:

  1. 项目需要实施#attributes
  2. item.attributes需要返回一组响应#name#value
  3. 的对象

    现在,让我们从一个项目的角度思考。   - 项目具有许多属性(例如,名称和样式)。   - 可以在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