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
我会预期以上输出:
--- !ruby/object:Test a: 29 b: 3 29 3 2 --- !ruby/object:Test a: 4 b: 3 29 3 4
相反,我得到:
--- !ruby/object:Test a: 29 b: 3 29 3 1 --- !ruby/object:Test a: 4 b: 3 4 3 2
解决方法
例如,考虑一个初始化的对象,看起来像这样(没有其他实例变量):
def initialize(param_one,param_two) @a_variable = some_calculation(param_one,param_two) end
现在当一个实例被反序列化时,Yaml处理器的值为@a_variable,但是initialize方法需要两个参数,所以它不能调用它.即使实例变量的数量与初始化参数的数量相匹配,并不一定是它们对应的情况,即使处理器不知道它们被传递到初始化的顺序.
将Ruby对象序列化和反序列化为Yaml的默认过程是在序列化期间写出所有实例变量(使用其名称),然后在反序列化时分配类的新实例,并在此新实例上简单设置相同的实例变量.
当然有时你需要更多的控制这个过程.如果您使用的是Psych Yaml处理器(这是Ruby 1.9.3中的默认设置),那么您应该适当地实现encode_with(用于序列化)或init_with(用于反序列化)方法.
为了序列化,Psych将调用一个对象的encode_with方法,如果它存在,传递一个coder
object.这个对象允许你指定如何在Yaml中表示对象 – 通常你只是像哈希一样对待它.
对于反序列化,Psych会调用init_with方法,如果它存在于您的对象上,而不是使用上述默认过程,再次传递一个编码器对象.这一次,编码器将包含关于Yaml中对象表示的信息.
请注意,您不需要同时提供这两种方法,如果需要,您可以提供任何一种方法.如果同时提供这两个,那么在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
当你将这个类的一个实例转储给Yaml时,它会看起来像这样,没有计算的值:
--- !ruby/object:Foo first: 1 second: 2
当您将此Yaml加载到Ruby中时,创建的对象将设置@calculated实例变量.
如果你希望你可以在init_with内调用initialize,但是我认为在初始化一个新类的实例之前要清楚一点,并且从Yaml反序列化一个现有的实例.我会建议将常用逻辑提取到可以从两个方面调用的方法,