Lazy evaluation in Ruby

亡梦爱人 提交于 2019-12-03 02:02:20

问题


I have a situation for Ruby, where an object is possibly necessary to be created, but it is not sure. And as the creation of the object might be costly I am not too eager creating it. I think this is a clear case for lazy loading. How can I define an object which is not created only when someone sends a message to it? The object would be created in a block. Is there a way for simple lazy loading/initialisation in Ruby? Are these things supported by some gems, which provide different solutions for various cases of lazy initialisation of objects? Thanks for your suggestions!


回答1:


There are two ways.

The first is to let the caller handle lazy object creation. This is the simplest solution, and it is a very common pattern in Ruby code.

class ExpensiveObject
  def initialize
    # Expensive stuff here.
  end
end

class Caller
  def some_method
    my_object.do_something
  end

  def my_object
    # Expensive object is created when my_object is called. Subsequent calls
    # will return the same object.
    @my_object ||= ExpensiveObject.new
  end
end

The second option is to let the object initialise itself lazily. We create a delegate object around our actual object to achieve this. This approach is a little more tricky and not recommended unless you have existing calling code that you can't modify, for example.

class ExpensiveObject        # Delegate
  class RealExpensiveObject  # Actual object
    def initialize
      # Expensive stuff here.
    end

    # More methods...
  end

  def initialize(*args)
    @init_args = args
  end

  def method_missing(method, *args)
    # Delegate to expensive object. __object method will create the expensive
    # object if necessary.
    __object__.send(method, *args)
  end

  def __object__
    @object ||= RealExpensiveObject.new(*@init_args)
  end
end

# This will only create the wrapper object (cheap).
obj = ExpensiveObject.new

# Only when the first message is sent will the internal object be initialised.
obj.do_something

You could also use the stdlib delegate to build this on top of.




回答2:


If you want to lazily evaluate pieces of code, use a proxy:

class LazyProxy

  # blank slate... (use BasicObject in Ruby 1.9)
  instance_methods.each do |method| 
    undef_method(method) unless method =~ /^__/
  end

  def initialize(&lazy_proxy_block)
    @lazy_proxy_block = lazy_proxy_block
  end

  def method_missing(method, *args, &block)
    @lazy_proxy_obj ||= @lazy_proxy_block.call # evaluate the real receiver
    @lazy_proxy_obj.send(method, *args, &block) # delegate unknown methods to the real receiver
  end
end

You then use it like this:

expensive_object = LazyProxy.new { ExpensiveObject.new }
expensive_object.do_something

You can use this code to do arbitrarily complex initialization of expensive stuff:

expensive_object = LazyProxy.new do
  expensive_helper = ExpensiveHelper.new
  do_really_expensive_stuff_with(expensive_helper)
  ExpensiveObject.new(:using => expensive_helper)
end
expensive_object.do_something

How does it work? You instantiate a LazyProxy object that holds instructions on how to build some expensive object in a Proc. If you then call some method on the proxy object, it first instantiates the expensive object and then delegates the method call to it.



来源:https://stackoverflow.com/questions/2461432/lazy-evaluation-in-ruby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!