Как запустить код до и после метода в подклассе? - PullRequest
6 голосов
/ 07 июля 2010

Мои первые мысли примерно такие:

class AbstractBuilder
  attr_reader :time_taken

  def build_with_timer
    started_at = Time.now
    build
    @time_taken = Time.now - started_at
  end

  def build
    raise 'Implement this method in a subclass' 
  end
end

class MyBuilder < AbstractBuilder
  def build
    sleep(5)
  end
end

builder = MyBuilder.new.build_with_timer
puts builder.time_taken

Я подозреваю, что есть лучший способ, который предлагает большую гибкость, например, в идеале я хотел бы вызвать 'build' для экземпляра MyBuilder вместо 'build_with_timer' и всегда записывать время выполнения.

Я подумал о том, чтобы использовать alias_method из initialize или даже использовать модуль mixin вместо наследования классов, который переопределил бы метод сборки, вызывающий super в середине (не уверен, сработает ли это). Прежде чем я спустился в кроличью нору, я подумал, что увижу, есть ли установленная практика.

Ответы [ 4 ]

4 голосов
/ 11 июля 2010

У меня был удар по версии, чтобы добиться того, что вы хотите. Эта версия не требует, чтобы у подкласса был дополнительный код.

class AbstractBuilder

  @@disable_override = false

  def before_method
    puts "before"
  end

  def after_method
    puts "after"
  end

  def self.method_added name
    unless @@disable_override
      if name == :build
        @@disable_override = true # to stop the new build method 
        self.send :alias_method, :sub_build, :build
        self.send :remove_method, :build
        self.send :define_method, :build do
          before_method
          sub_build
          after_method
        end
        @@disable_override = false
      else
        puts "defining other method #{name}"
      end
    end
  end

end

class MyBuilder < AbstractBuilder

  def build
    puts "starting build"
    sleep(5)
    puts "built."
  end

  def unnaffected_method
    # this method won't get redefined
  end

end

b = MyBuilder.new
b.build

Выходы

defining other method unnaffected_method
before
starting build
built.
after
3 голосов
/ 07 июля 2010

Я бы поиграл с alias_method:

module Timeable
  def time_methods *meths
    meths.each do |meth|
      alias_method "old_#{meth}", meth

      define_method meth do |*args|
        started_at = Time.now
        res = send "old_#{meth}", *args
        puts "Execution took %f seconds" % (Time.now - started_at)
        res
      end
    end
  end

end

class Foo
  def bar str
    puts str
  end
end

Foo.extend Timeable
Foo.time_methods :bar
Foo.new.bar('asd')
#=>asd
#=>Execution took 0.000050 seconds
0 голосов
/ 09 августа 2010

Это пример использования определения из учебника для Аспектно-ориентированного программирования .Это обычно предлагает более чистое разделение проблем.На этой арене Ruby предлагает Аквариум и AspectR .Однако вы можете не захотеть добавлять другую зависимость в ваш проект.Таким образом, вы все еще можете рассмотреть возможность использования одного из других подходов.

0 голосов
/ 07 июля 2010

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

class AbstractBuilder
  attr_reader :time_taken

  def construct! # i.e., build, and also call your hooks
    @@prebuild.each { |sym| self.send(sym) }
    build
    @@postbuild.each { |sym| self.send(sym) }
  end  

  def construct_with_timer
    started_at = Time.now
    construct!
    @time_taken = Time.now - started_at

    puts "!!! Build time: #@time_taken"
  end

  class << self
    def before_build(fn); @@prebuild ||= []; @@prebuild << fn; end
    def after_build(fn);  @@postbuild ||= []; @@postbuild << fn; end
  end
end

class MyBuilder < AbstractBuilder
  before_build :preprocess
  after_build  :postprocess

  def build; puts "BUILDING"; sleep(3); end
  def preprocess;  puts "Preparing to build..."; end
  def postprocess; puts "Done building. Thank you for waiting."; end
end

builder = MyBuilder.new
builder.construct_with_timer

# => Preparing to build...
# => BUILDING
# => Done building. Thank you for waiting.
# => !!! Build time: 3.000119
...