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

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

Ответы

Ответ 1

Есть два способа.

Во-первых, пусть caller обрабатывает создание ленивого объекта. Это самое простое решение, и это очень распространенный шаблон в 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, чтобы создать это поверх.

Ответ 2

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

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. Если вы затем вызываете какой-либо метод в прокси-объекте, он сначала запускает дорогостоящий объект и затем делегирует вызов метода ему.