我的第一个想法是这样的事情:
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'而不是'build_with_timer',并且总是记录执行时间。
我确实考虑过使用初始化的alias_method,甚至使用模块mixin而不是类继承,它会覆盖在中间调用super的构建方法(不确定是否可行)。在我走下兔子洞之前,我想我会看看是否有既定的做法。
答案 0 :(得分:4)
我对一个版本进行了攻击以达到你想要的效果。此版本不要求子类具有任何额外的代码。
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
答案 1 :(得分:3)
我会玩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
答案 2 :(得分:0)
听起来你正在寻找对象生命周期事件的钩子。你必须将它构建到你的基础对象中并提供一点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
答案 3 :(得分:0)
这是Aspect-Oriented Programming的教科书定义用例。它通常提供更清晰的关注点分离。在这个领域,Ruby提供Aquarium和AspectR。但是,您可能不希望向项目添加其他依赖项。因此,您可能仍会考虑使用其他方法之一。