如何在子类中的方法之前和之后运行代码?

时间:2022-03-08 22:21:41

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。但是,您可能不希望向项目添加另一个依赖项。因此,您可能仍会考虑使用其他方法之一。