2010-03-17 2 views
15

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

+0

Вместо того, чтобы кататься самостоятельно, вы можете использовать [lazy.rb] (https://github.com/mental/lazy). Есть несколько примеров использования в книге [Ruby Best Practices] (http://oreilly.com/catalog/9780596523015/), см. Стр. 123 и вперед. –

ответ

31

Существует два способа.

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

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

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, чтобы построить это на вершине.

+0

В первом примере мне нужно сохранить экземпляр класса Caller. Правильно? Но в чем разница для меня - сохранить экземпляр класса Caller или сохранить экземпляр Expensive класса? – demas

+0

В первом примере класс 'Caller' является всего лишь примером того, как вы бы * использовали * класс ExpensiveObject. Разница: введите лень, где вы * используете * ExpensiveObject' (простое) или вводите лень в '' ExpensiveObject' * * (немного сложнее). – molf

+1

@molf: всякий раз, когда вы переопределяете 'method_missing', вы * должны * также переопределять' reply_to? '(Или предпочтительно' response_to_missing? 'В 1.9.2). См. Http://blog.marc-andre.ca/2010/11/methodmissing-politely.html – Nemo157

5

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

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