【问题标题】:How to make alias_method in Ruby use the sub-class' custom method?如何让 Ruby 中的 alias_method 使用子类的自定义方法?
【发布时间】:2019-11-05 14:42:33
【问题描述】:

假设我有一个包含三个子类的基类。基类有一个大多数子类共有的方法,并且它有一个别名:

class Beer
  def bottle_content
    '250 ml'
  end
  alias_method :to_s, :bottle_content
end

class Heineken < Beer
end

class Stella < Beer
end

class Duvel < Beer
  def bottle_content
    '330 ml'
  end
end

现在,如果在Duvel 的发散子类实例上调用to_s 方法,则返回250 ml 而不是330 ml

我明白为什么了;别名是在超类的级别上创建的。我知道这可以通过在分歧类中重新定义alias_method 来解决。但是还有其他方法吗?

显然,使用to_s 的方法会起作用:

class Beer
  def bottle_content
    '250 ml'
  end
  def to_s; bottle_content; end
end

但也许还有更优雅的方法?

【问题讨论】:

    标签: ruby class inheritance


    【解决方案1】:

    如果您想要的只是委托行为而无需手动编写新方法,我可能会建议Forwardable

    require 'forwardable'
    
    class Beer
      extend Forwardable
    
      def bottle_content
        '250 ml'
      end
    
      def_delegator :self, :bottle_content, :to_s
    end
    

    它确实旨在用于将方法委派给其他对象,但没有什么说我们不能这样做,只需将:self作为第一个参数。

    irb(main):001:0> puts Duvel.new
    330 ml
    => nil
    

    【讨论】:

    • 小心点,在 Rails 中扩展 this 会覆盖覆盖的 Module.delegate 方法
    【解决方案2】:

    这是因为alias_method 在运行时处理self

    编辑 正如@Jörg 评论的那样,我的解决方案可能会根据您的应用程序(多线程环境......)引起一些麻烦。像您建议的 to_s 实现可能是最好的,而不是特别不那么优雅的 IMO。

    一种方法是在初始化时(在类上)声明别名。

    class Beer
      def initialize
        self.class.alias_method :to_s, :bottle_content
      end
    
      def bottle_content
        '250 ml'
      end
    end
    

    这样别名是在实例的类上创建的,即。 Duvel 在您的示例中。

    【讨论】:

    • 然而,这意味着您只能有一个子类,因为对于您创建的每个实例,您都会覆盖该方法。因此,如果您有不同的子类,您将用不同的实现覆盖该方法,这意味着无论您最后实例化哪个类,all 对象的行为都是如此,而不管它们是哪个子类的实例。现在,由于您的所有子类最终都会表现相同,因此您可能根本没有不同的子类,如果您只有一个子类,那么您可能根本没有子类。
    • @colinux 你说得对,毕竟方法方式可能是最优雅的。
    【解决方案3】:

    由于我将解释的原因,您不能在创建子类时从 Beer 为子类中的方法创建别名。

    然而,使用回调Class#inherited,我们可以做一些接近你想要实现的事情:

    class Beer
      def bottle_content
        '250 ml'
      end
    
      alias_method :to_s, :bottle_content
    
      def self.inherited(klass)
        klass.define_method(:to_s) { bottle_content }
      end
    end
    
    class Duvel < Beer
      def bottle_content
        '330 ml'
      end
    end
    

    Beer.new.to_s
      #=> "250 ml" 
    Duvel.new.to_s
      #=> "330 ml"
    

    问题是Beer::inherited 是在class Duvel &lt; Beer 执行之后,在方法@​​987654327@ 定义之前调用的。如果我们将Beer::inherited的操作行改为

    klass.alias_method(:to_s, :bottle_content)
    

    这会将Duvel#to_s 绑定到Beer#bottle_content,因为尚未定义Duvel#bottle_content

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-24
      • 2021-08-15
      • 1970-01-01
      • 2015-03-12
      相关资源
      最近更新 更多