My first thoughts are some thing like this:
我的第一个想法是这样的事情:
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
I would suspect there is a better way which offers better flexibility, for example ideally I'd like to call 'build' on an instance of MyBuilder instead of 'build_with_timer' and always have the execution time recorded.
我怀疑有一种更好的方法可以提供更好的灵活性,例如理想情况下我想在MyBuilder的实例上调用'build'而不是'build_with_timer'并且总是记录执行时间。
I did consider using alias_method from initialize or even using a module mixin instead of class inheritance which would override the build method calling super in the middle (not sure if that would work). Before I go down the rabbit hole I thought I'd see if there is an established practice.
我确实考虑过使用初始化的alias_method,甚至使用模块mixin而不是类继承,它会覆盖在中间调用super的构建方法(不确定是否可行)。在我走下兔子洞之前,我想我会看看是否有既定的做法。
4 个解决方案
#1
3
I'd play with 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
#2
4
I had a stab at a version to achieve what you want. This version doesn't require the subclass to have any extra code either.
我有一个版本来实现你想要的东西。此版本不要求子类具有任何额外的代码。
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
Outputs
defining other method unnaffected_method
before
starting build
built.
after
#3
0
Sounds like you're looking for hooks into object lifecycle events. You'll have to build this into your base object and provide a little DSL -- I'm thinking you're after something like ActiveRecord Callbacks. Here's how we might modify your example to allow something like that:
听起来你正在寻找对象生命周期事件的钩子。你必须将它构建到你的基础对象中并提供一点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
#4
0
This is a textbook-definition use case for Aspect-Oriented Programming. It generally offers a cleaner separation of concerns. In this arena, Ruby offers Aquarium and AspectR. However, you may not want to add another dependency to your project. As such, you might still consider using one of the other approaches.
这是面向方面编程的教科书定义用例。它通常提供更清晰的关注点分离。在这个舞台上,Ruby提供水族馆和AspectR。但是,您可能不希望向项目添加另一个依赖项。因此,您可能仍会考虑使用其他方法之一。
#1
3
I'd play with 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
#2
4
I had a stab at a version to achieve what you want. This version doesn't require the subclass to have any extra code either.
我有一个版本来实现你想要的东西。此版本不要求子类具有任何额外的代码。
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
Outputs
defining other method unnaffected_method
before
starting build
built.
after
#3
0
Sounds like you're looking for hooks into object lifecycle events. You'll have to build this into your base object and provide a little DSL -- I'm thinking you're after something like ActiveRecord Callbacks. Here's how we might modify your example to allow something like that:
听起来你正在寻找对象生命周期事件的钩子。你必须将它构建到你的基础对象中并提供一点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
#4
0
This is a textbook-definition use case for Aspect-Oriented Programming. It generally offers a cleaner separation of concerns. In this arena, Ruby offers Aquarium and AspectR. However, you may not want to add another dependency to your project. As such, you might still consider using one of the other approaches.
这是面向方面编程的教科书定义用例。它通常提供更清晰的关注点分离。在这个舞台上,Ruby提供水族馆和AspectR。但是,您可能不希望向项目添加另一个依赖项。因此,您可能仍会考虑使用其他方法之一。