Отложенные вычисления в Ruby

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

18
задан cjapes 25 April 2012 в 15:14
поделиться

2 ответа

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

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

36
ответ дан 30 November 2019 в 06:59
поделиться

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

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

6
ответ дан 30 November 2019 в 06:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: