Ленивая оценка в Ruby - PullRequest
       30

Ленивая оценка в Ruby

18 голосов
/ 17 марта 2010

У меня есть ситуация для Ruby, где возможно необходимо создать объект, но он не уверен. И поскольку создание объекта может быть дорогостоящим, я не слишком стремлюсь создать его. Я думаю, что это явный случай для ленивой загрузки. Как я могу определить объект, который не создается, только когда кто-то отправляет ему сообщение? Объект будет создан в блоке. Есть ли способ для простой отложенной загрузки / инициализации в Ruby? Поддерживаются ли эти вещи некоторыми драгоценными камнями, которые предоставляют различные решения для разных случаев ленивой инициализации объектов? Спасибо за ваши предложения!

Ответы [ 2 ]

35 голосов
/ 17 марта 2010

Есть два пути.

Первое - позволить вызывающей стороне обрабатывать создание ленивых объектов. Это простейшее решение, и это очень распространенный шаблон в коде Ruby.

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

Второй вариант - позволить объекту лениво инициализироваться. Мы создаем объект делегата вокруг нашего фактического объекта, чтобы достичь этого. Этот подход немного сложнее и не рекомендуется, если у вас нет существующего кода вызова, который вы не можете изменить, например.

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

Вы также можете использовать stdlib delegate, чтобы построить его поверх.

6 голосов
/ 17 марта 2010

Если вы хотите лениво оценивать фрагменты кода, используйте прокси:

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

Затем вы используете это так:

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

Вы можете использовать этот код для произвольной сложной инициализации дорогих вещей:

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

Как это работает? Вы создаете экземпляр объекта LazyProxy, который содержит инструкции о том, как построить дорогой объект в Proc. Если затем вы вызываете какой-либо метод для прокси-объекта, он сначала создает экземпляр дорогого объекта, а затем делегирует ему вызов метода.

...