Lazy evaluation in Ruby

后端 未结 2 683
闹比i
闹比i 2021-01-30 14:40

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 creati

相关标签:
2条回答
  • 2021-01-30 15:25

    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.

    0 讨论(0)
  • 2021-01-30 15:41

    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.

    0 讨论(0)
提交回复
热议问题