3

我有一个包装任意数据单元的类;一种过滤器。这些单元存在于后端数据存储中。但这应该尽可能透明。

编写简单的访问器很简单:

def foo
  # fetch backend cell value and return it
end
def foo=(val)
  # store val in backend cell
end

我发现棘手的部分是拦截和跟踪方法,如果数据没有被包装,这些方法通常会影响数据。例如,如果数据是一个数组,则会在原位obj.foo << 17向数组中添加一个元素。我想在后端存储的数据上保持这种行为(,导致存储的值也添加了一个元素)。我想也许 a会有所帮助:obj.foo << 17method_missing

def method_missing(meth, *args)
  methsym = meth.to_sym
  curval = self.get
  lastval = curval.clone
  opresult = curval.__send__(methsym, *args)
  if (curval != lastval)
    self.set(curval)
  end
  return opresult
end

但是结合阅读器访问器,操作的控制已经超出了我的范围,因为它返回的东西不是东西本身。(,如果后端数据是一个数组,我将返回它的一个副本,并且该副本正在被修改并且从未发回给我。)

这可能吗?如果是这样,我该怎么做?(这可能很明显,我只是想念它,因为我累了——或者可能不是。:-)

谢谢!

[编辑]

换句话说.. #method_missing允许您挂钩未知方法的调用过程。我正在寻找一种方法来类似地挂接到调用过程中,但是对于所有已知未知的方法。

谢谢!

4

2 回答 2

3

您需要将您的类返回的每个对象包装在一个元对象中,该对象知道后端,并可以根据需要对其进行更新。

在您的示例中,您需要返回一个可以处理插入、删除等的数组包装对象。

- - 编辑 - -

您可以向返回的对象添加“单例方法”,而不是创建大量包装类,特别是如果您可以轻松识别可能需要特殊处理的方法。

module BackEndIF
  alias :old_send :__send__
  def __send__ method, *args
    if MethodsThatNeedSpecialHandling.include?(method)
       doSpecialHandling()
    else
      old_send(method,args)
    end
  end
end

#in your class:
def foo
   data = Backend.fetch(query)
   data.extend(BackEndIF)
   return data
end

我认为任何基于缺少方法的方法都行不通,因为您返回的对象确实具有相关方法。(即数组确实有一个运算符<<,它没有丢失)

或者,也许你可以用method_missing你概述的那样做一些事情。创建一个类似这样的 meta_object:

class DBobject
   def initialize(value, db_reference)
      @value = value
      @ref = db_reference
    end
   def method_missing(meth, *args)
     old_val = @value
     result = @value.__send__(meth, *args)
     DatabaseUpdate(@ref, @value) if (@value != old_val)
     return result   
   end
end

然后foo返回一个DBObject.new(objectFromDB, referenceToDB).

于 2011-08-29T21:19:54.893 回答
0

Delegator我通过从模块中借用解决了这个问题。(下面的代码不能保证有效;我已经手动编辑了一些细节。但它应该提供要点。)

  • 在 fetch(读取器访问器)上,使用修改后的方法注释要传回的值:

    def enwrap(target)
      #
      # Shamelessly cadged from delegator.rb
      #
      eigenklass = eval('class << target ; self ; end')
      preserved = ::Kernel.public_instance_methods(false)
      preserved -= [ 'to_s', 'to_a', 'inspect', '==', '=~', '===' ]
      swbd = {}
      target.instance_variable_set(:@_method_map, swbd)
      target.instance_variable_set(:@_datatype, target.class)
      for t in self.class.ancestors
        preserved |= t.public_instance_methods(false)
        preserved |= t.private_instance_methods(false)
        preserved |= t.protected_instance_methods(false)
      end
      preserved << 'singleton_method_added'
      target.methods.each do |method|
        next if (preserved.include?(method))
        swbd[method] = target.method(method.to_sym)
        target.instance_eval(<<-EOS)
          def #{method}(*args, &block)
            iniself = self.clone
            result = @_method_map['#{method}'].call(*args, &block)
            if (self != iniself)
              #
              # Store the changed entity
              #
              newklass = self.class
              iniklass = iniself.instance_variable_get(:@_datatype)
              unless (self.kind_of?(iniklass))
                begin
                  raise RuntimeError('Class mismatch')
                rescue RuntimeError
                  if ($@)
                    $@.delete_if { |s|
                      %r"\A#{Regexp.quote(__FILE__)}:\d+:in `" =~ s
                    }
                  end
                  raise
                end
              end
              # update back end here
            end
            return result
          end
        EOS
      end
    end                         # End of def enwrap
    
  • 在 store(writer 访问器)上,去掉我们添加的单例方法:

    def unwrap(target)
      remap = target.instance_variable_get(:@_method_map)
      return nil unless (remap.kind_of?(Hash))
      remap.keys.each do |method|
        begin
          eval("class << target ; remove_method(:#{method}) ; end")
        rescue
        end
      end
      target.instance_variable_set(:@_method_map, nil)
      target.instance_variable_set(:@_datatype, nil)
    end                        # End of def unwrap
    

因此,当请求该值时,它会在返回之前添加“包装器”方法,并且在将任何内容存储到后端之前删除单例。任何更改值的操作也会作为副作用更新后端。

目前实施的这种技术有一些不幸的副作用。假设带有包装变量的类在 中实例化backend,并且其中一个变量通过 ivar_foo以下方式访问:

backend.ivar_foo
=> nil
backend.ivar_foo = [1, 2, 3]
=> [1,2,3]
bar = backend.ivar_foo
=> [1,2,3]
bar << 4
=> [1,2,3,4]
backend.ivar_foo = 'string'
=> "string"
bar
=> [1,2,3,4]
backend.ivar_foo
=> "string"
bar.pop
=> 4
bar
=> [1,2,3]
backend.ivar_foo
=> [1,2,3]

但这对我来说更多的是好奇而不是问题。:-)

感谢您的帮助和建议!

于 2011-09-06T17:39:19.733 回答