Ruby YAML解析器通过传递构造函数

cle*_*ine 9 ruby yaml

我正在开发一个应用程序,它从YAML文件中获取输入,将它们解析为对象,然后让它们完成它们的工作.我现在唯一的问题是,YAML解析器似乎忽略了对象"initialize"方法.我指望构造函数填充YAML文件缺少的任何实例变量,以及在类变量中存储一些东西.这是一个例子:

class Test

    @@counter = 0

    def initialize(a,b)
        @a = a
        @b = b

        @a = 29 if @b == 3

        @@counter += 1
    end

    def self.how_many
        p @@counter
    end

    attr_accessor :a,:b

end

require 'YAML'

a = Test.new(2,3)
s = a.to_yaml
puts s
b = YAML::load(s)
puts b.a
puts b.b
Test.how_many

puts ""

c = Test.new(4,4)
c.b = 3
t = c.to_yaml
puts t
d = YAML::load(t)
puts d.a
puts d.b
Test.how_many
Run Code Online (Sandbox Code Playgroud)

我希望以上输出:

--- !ruby/object:Test
a: 29
b: 3
29
3
2

--- !ruby/object:Test
a: 4
b: 3
29
3
4
Run Code Online (Sandbox Code Playgroud)

相反,我得到了:

--- !ruby/object:Test
a: 29
b: 3
29
3
1

--- !ruby/object:Test
a: 4
b: 3
4
3
2
Run Code Online (Sandbox Code Playgroud)

我不明白如何在不使用定义的initialize方法的情况下制作这些对象.我也想知道是否还有强制解析器使用initialize方法.

mat*_*att 10

从Yaml反序列化对象不使用该initialize方法,因为通常在对象的实例变量(这是默认的Yaml序列化存储的变量)和参数之间没有对应关系initialize.

例如,考虑一个initialize看起来像这样的对象(没有其他实例变量):

def initialize(param_one, param_two)
  @a_variable = some_calculation(param_one, param_two)
end
Run Code Online (Sandbox Code Playgroud)

现在当反序列化一个实例时,Yaml处理器有一个值@a_variable,但该initialize方法需要两个参数,所以它不能调用它.即使实例变量的数量与参数的数量相匹配,initialize也不一定是它们对应的情况,即使它们确实如此,处理器也不知道它们应该传递给它们的顺序initialize.

将Ruby对象序列化和反序列化为Yaml的默认过程是在序列化期间写出所有实例变量(及其名称),然后在反序列化时分配新的类实例并在此新实例上设置相同的实例变量.

当然,有时您需要更多地控制此过程.如果您正在使用Psych Yaml处理器(这是Ruby 1.9.3中的默认值),那么您应该适当地实现encode_with(用于序列化)或或init_with(用于反序列化)方法.

对于序列化,Psych会调用encode_with对象的方法(如果它存在),传递一个coder对象.此对象允许您指定如何在Yaml中表示对象 - 通常您只需将其视为哈希.

对于反序列化,Psych会调用init_with方法,如果它存在于您的对象上,而不是使用上述默认过程,再次传递一个coder对象.这次coder将包含有关Yaml中对象表示的信息.

请注意,您不需要提供这两种方法,如果需要,您可以提供任何一种方法.如果同时提供这两个,则coder传入的对象init_with将基本上与encode_with该方法运行后传递的对象相同.

例如,考虑一个对象具有一些从其他实例变量计算的实例变量(可能作为优化以避免大量计算),但不应序列化为Yaml.

class Foo

  def initialize(first, second)
    @first = first
    @second = second
    @calculated = expensive_calculation(@first, @second)
  end

  def encode_with(coder)
    # @calculated shouldn’t be serialized, so we just add the other two.
    # We could provide different names to use in the Yaml here if we
    # wanted (as long as the same names are used in init_with).
    coder['first'] = @first
    coder['second'] = @second
  end

  def init_with(coder)
    # The Yaml only contains values for @first and @second, we need to
    # recalculate @calculated so the object is valid.
    @first = coder['first']
    @second = coder['second']
    @calculated = expensive_calculation(@first, @second)
  end

  # The expensive calculation
  def expensive_calculation(a, b)
    ...
  end
end
Run Code Online (Sandbox Code Playgroud)

当您将此类的实例转储到Yaml时,它看起来像这样,没有calculated值:

--- !ruby/object:Foo
first: 1
second: 2
Run Code Online (Sandbox Code Playgroud)

当您将此Yaml加载回Ruby时,创建的对象将@calculated设置实例变量.

如果你想,你可以调用initialize来自内部init_with,但我认为这将是更好地保持初始化之间的明确分离类的实例,并反序列化现有的YAML实例.我建议将常用逻辑提取到可以从两者调用的方法中,