ruby - 通过类层次结构进行链接,无需调用 `super` 或发明新名称

标签 ruby inheritance

我有使用super的方法:

class A
  def say txt
    puts "A.say from #{self}: #{txt}"
  end
  def run
    puts "A.run from #{self}: I am preparing something here..."
  end
end

class B < A
  def run
    super
    say 'I am B, adding 1'
    say 'I am B, adding 2'
  end
end

class C < A
  def run
    super
    say 'I am C, adding 3'
    say 'I am C, adding 4'
  end
end

class D < C
  def run
    super
    say 'I am D, adding 5'
  end
end

我想从子类中删除super,并想出了这个:

class A
  def say txt
    puts "A.say from #{self}: #{txt}"
  end
  def run
    puts "A.run from #{self}: I am preparing something here..."
    run_sub
  end
  def run_sub
    # do nothing by default
  end
end

class B < A
  def run_sub
    say 'I am B, adding 1'
    say 'I am B, adding 2'
  end
end

class C < A
  def run_sub
    say 'I am C, adding 3'
    say 'I am C, adding 4'
    run_sub_sub
  end
  def run_sub_sub
    # do nothing by default
  end
end

class D < C
  def run_sub_sub
    say 'I am D, adding 5'
  end
end

只有叶类真正受益于此。因此,我想到将 run 方法的代码保留为 block ,并尝试按类层次结构的顺序调用它们。代码如下:

class A
  def say txt
    puts "A.say from #{self}: #{txt}"
  end
  def self.run &block
    @run_block = block
  end
  def self.all_run_blocks
    if self == A
      return [@run_block]
    else
      return superclass.all_run_blocks + [@run_block]
    end
  end
  run do
    puts "A.run from #{self}: I am preparing something here..."
  end
  def run_all
    self.class.all_run_blocks.each do |block|
      block.call unless block.nil?
    end
  end
end

class B < A
  run do
    say 'I am B, adding 1'
    say 'I am B, adding 2'
  end
end

class C < A
  run do
    say 'I am C, adding 3'
    say 'I am C, adding 4'
  end
end

class D < C
  run do
    say 'I am D, adding 5'
  end
end

b = B.new
d = D.new

b.run_all
d.run_all

但这不起作用,因为传递给 run 类方法的 block 保留在类的范围内,因此不知道 say 实例方法。

有没有更好的方法来实现这样的目标?

最佳答案

首先我想说这是一个非常有趣的问题,如果可以的话我会再次投票。感谢您提出一个实际上需要一些思考和努力的问题,我希望我的回答能够满足您的要求。

注意:我使用了您的明确示例,因此此方法不接受参数。处理参数也需要一些额外的工作。

为什么不将其设为模块,这样您就可以在任何地方使用它?

module Appendable

  def self.extended(base)
    base.include(InstanceMethods)
  end

  def append_method(method_name,&block)
    alias_name = create_alias(method_name)
    define_method(method_name) do
      run_callbacks(self,alias_name) do |object|
        object.instance_eval &block
      end
    end
  end

  module InstanceMethods
    def run_callbacks(object,method_name)
      object.send(method_name)
      yield(self)
    end
  end

  private
    def create_alias(method_name)
      alias_name = "_#{method_name}_callback_#{self.name}"
      alias_method alias_name, method_name
      alias_name
    end
end

它的作用只是将原始方法(在本例中是run)方法别名为_METHODNAME_callback_CLASSNAME,然后重新定义原始方法以首先调用别名版本,这会创建一个沿着链向上递​​归,然后返回到类中给出的 block 。

然后你只需像这样定义类:

class A
  extend Appendable

  def run
    say "A"
  end

  def say(txt)
    # I used puts to make it evident that it ran up the chain
    puts "#{self.class.name} said #{txt}"
  end
end
class B < A 
  append_method :run do 
    say "B"
  end
end
class C < B 
  append_method :run do 
    say "C"
  end
end     
class D < A 
  append_method :run do 
    say "D"
  end
end
class E < A
   def run 
     puts "E overwrote run"
   end
end 
class F < E;append_method(:run) { say "F" }; end

这将允许您的所有类继承以前的分层方法,例如

A.new.run
  A said A
B.new.run   
  B said A
  B said B
C.new.run
  C said A
  C said B
  C said C 
D.new.run 
  D said A
  D said D
F.new.run
  E overwrote run
  F said F 

这似乎是您正在寻找的整体效果,除非我误解了?

关于ruby - 通过类层次结构进行链接,无需调用 `super` 或发明新名称,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30657147/

相关文章:

java - "Constructor call must be the first statement in a constructor"Java 代码中出现错误?

python - python中具有抽象方法的委托(delegate)设计模式

python - 是否有任何设计模式可以实现抽象类的子类方法的装饰?

inheritance - 时间:2019-03-14 标签:c++/clisintaxis: Error: 'new' or 'override' is required because this declaration matches function

ruby - 正则表达式否定后视忽略?

方法名称中的 Ruby 和整数

ruby-on-rails - 多对多 : has_many :through and User table relation issue in rails 4

javascript - 创建 javascript 子类型的实例会产生意外行为

mysql - 无法在 Win 2008R2 上安装 Redmine .. gem install activerecord-mysql-adapter 不工作

ruby-on-rails - ajax 从 Rails 应用程序发布到外部数据库