Ruby 通过类层次结构链接而不调用“super”或发明新名称

Ruby 通过类层次结构链接而不调用“super”或发明新名称,ruby,inheritance,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

我有使用
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
实际上只有叶类从中受益。因此,我考虑将
run
方法的代码保持为块,并尝试按类层次结构的顺序调用它们。代码如下:

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
A类
def say txt
放置“A.say from#{self}:#{txt}”
结束
def自运行和阻塞
@运行块=块
结束
def self.all_run_块
如果self==A
返回[@run_block]
其他的
返回超类。所有运行块+[@run\u block]
结束
结束
跑步
放上“A.从#{self}开始跑步:我正在准备一些东西……”
结束
def run_all
self.class.all_run_block.each do|block|
block.call,除非block.nil?
结束
结束
结束
B类
但这不起作用,因为传递给
run
类方法的块仍在类的范围内,因此不知道
say
实例方法

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

模块#prepend
是否正是为了实现这一目标:

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

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

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

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

根据(谢谢!)中的建议,我得出以下结论:

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
  def run_all
    self.class.ancestors.reverse.each do |ancestor|
      # check if ancestor is a subclass of A
      if ancestor.ancestors.include? A 
        # check if the run method is actually from this ancestor
        if ancestor.instance_method(:run).owner == ancestor
          # call the run method in the context of the given instance
          ancestor.instance_method(:run).bind(self).call
        end
      end
    end
  end
end

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

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

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

b = B.new
d = D.new

b.run_all
d.run_all
A类
def say txt
放置“A.say from#{self}:#{txt}”
结束
def运行
放上“A.从#{self}开始跑步:我正在准备一些东西……”
结束
def run_all
self.class.祖先.reverse.each do|祖先|
#检查祖先是否是
如果祖先包括在内?A.
#检查run方法是否确实来自此祖先
如果祖先.instance_方法(:run).owner==祖先
#在给定实例的上下文中调用run方法
祖先.instance\u方法(:run.bind(self.call)
结束
结束
结束
结束
结束
B类
这不使用递归调用,而是通过类的
祖先
进行迭代,找到它们的
运行
方法,并按顺序调用它们


如果你知道如何改进,请告诉我

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

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

为什么不把它变成一个
模块
,这样你就可以在任何地方使用它了

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
在本例中)方法别名为
\u METHODNAME\u callback\u CLASSNAME
,然后重新定义原始方法,首先调用别名版本,该别名版本创建链上的递归,然后将
返回到类中给定的块

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

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

除非我误解了,否则这似乎就是你想要的整体效果?

run\u sub
在模式行话中被称为模板方法;这可能会帮你找到一些关于这个的资源。我认为你可能在某种元编程方面走对了方向。我可能会编写一个递归函数来遍历类层次结构,并使用相同的名称执行每个超类方法,直到超类不再定义该方法为止[self.class.superclass.instance_method(method).bind(self.call)]感谢您的关注。这段代码有两个问题:1)当“运行”
d
d
类的实例)时,它不会调用
C
run
(就像在我的原始代码中一样)和2)在原始代码中,
prepend A
行只是替换了
super
行,所以它不会更短。我修复了第一个问题。关于第二点,<代码>准备一个替换“<代码> <代码> >的必要性,因此,如果你坚持短代码,你可以考虑使用一个分号,如“代码”> B类,将其放在一行中;在A前面加上前缀
。我认为没有比这更好的方法了。缩短代码是本练习的全部目的:)…重点是
A
的子类应该能够扩展
run
函数,而不是替换它。这就是我想说的,在我的作品中提到了DSL(你在编辑它时删除了它)。类
B
C
D
是由“最终用户”编写的,应该尽可能简短、干净、清晰,这就是为什么我提出了块的想法(不幸的是,它没有起作用)。我认为@mahemoff的评论是正确的,但我还没有从中得到任何代码。看看我刚刚发布的模式,告诉我这是否是你想要的,或者你是否想要改变什么。
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