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