Мои первые мысли являются некоторой вещью как это:
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
Я подозревал бы, что существует лучший путь, который предлагает лучшую гибкость, например, идеально я хотел бы назвать 'сборку' на экземпляре MyBuilder вместо 'build_with_timer' и всегда записывать время выполнения.
Я действительно рассматривал использование alias_method от, инициализируют или даже использование модуля, смешивающегося вместо наследования классов, которое переопределило бы метод сборки, называющий супер в середине (не уверенный, если это будет работать). Прежде чем я спущусь по кроличьей норе, я думал, что буду видеть, существует ли установленная практика.
Я бы поиграл с 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
Я попробовал версию, чтобы добиться того, чего вы хотите. Эта версия также не требует, чтобы подкласс имел дополнительный код.
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
Похоже, вы ищете ловушки для событий жизненного цикла объекта. Вам нужно будет встроить это в свой базовый объект и предоставить небольшой 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