【问题标题】:Delegate attribute methods to the parent object将属性方法委托给父对象
【发布时间】:2013-12-03 01:41:58
【问题描述】:

我有以下课程:

class Alphabet

  attr_reader :letter_freqs, :statistic_letter

  def initialize(lang)
    @lang = lang
    case lang
    when :en
      @alphabet = ('A'..'Z').to_a
      @letter_freqs = { ... }
    when :ru
      @alphabet = ('А'..'Я').to_a.insert(6, 'Ё')
      @letter_freqs = { ... }
    ...
    end
    @statistic_letter = @letter_freqs.max_by { |k, v| v }[0]
  end

end

foo = Alphabet.new(:en)

这里的中心成员是@alphabet

我想让它成为某种容器类来直接调用数组方法,比如

foo[i]
foo.include?

而不是显式访问@alphabet

foo.alphabet[i]
foo.alphabet.include?  

我知道我可以定义很多方法,比如

def [](i)
  @alphabet[i]
end

但我正在寻找“继承”它们的正确方法。

【问题讨论】:

    标签: ruby delegation


    【解决方案1】:

    你可以使用Forwardable(它包含在Ruby标准库中):

    require 'forwardable'
    
    class Alphabet
    
      extend Forwardable
      def_delegators :@alphabet, :[], :include?
    
      def initialize
        @alphabet = ('A'..'Z').to_a
      end
    
    end
    
    foo = Alphabet.new
    
    p foo[0]           #=> "A"
    p foo.include? 'ç' #=> false
    

    如果您希望委托您的类未定义的所有方法,您可以使用SimpleDelegator(也在标准库中);它允许您将实例未响应的所有方法委托给__setobj__指定的对象:

    require 'delegate'
    
    class Alphabet < SimpleDelegator
    
      def initialize
        @alphabet = ('A'..'Z').to_a
        __setobj__(@alphabet)
      end
    
      def index
        'This is not @alphabet.index'
      end
    
    end
    
    foo = Alphabet.new
    
    p foo[0]           #=> "A"
    p foo.include? 'ç' #=> false
    p foo.index        #=> "This is not @alphabet.index"
    

    当委托不需要是动态的时,您可以将主类安排为 DelegateClass 的子类,传递要委托的类的名称作为参数并调用 super 传递要委托的对象在master类的#initialize方法中:

    class Alphabet < DelegateClass(Array)
    
      def initialize
        @alphabet = ('A'..'Z').to_a
        super(@alphabet)
      end
    

    更多关于 Ruby 中委托设计模式的信息here

    【讨论】:

    • 哇,太好了 :) 我可以委托所有方法吗?或者这不是一个好主意?
    • 不要委托所有方法,因为它们可能共享一些相同的方法名称,例如class,这可能会造成混乱。
    • 我的错,很好的答案!
    • @Zippie,接受这个答案,因为它有关于委派所有方法的信息。不过谢谢你的警告:)
    • 没问题,这是更好的答案
    【解决方案2】:

    您可以扩展 Forwardable 模块:

    class Alphabet
      require 'forwardable'
      extend Forwardable
      attr_accessor :alphabet
    
      def initialize
        @alphabet = [1,2,3]
      end
    
    
      def_delegator :@alphabet, :[], :include?
    end
    

    那么你可以这样做:

    alpha = Alphabet.new
    alpha[1]==hey.alphabet[1]
    => true
    

    警告:

    不要尝试委托所有方法(甚至不知道这是否可能),因为它们可能共享一些相同的方法名称,例如class,这可能会造成混乱。

    【讨论】:

      【解决方案3】:

      在 Ruby 中,您可以像这样扩展对象。

      class Array
        def second
          self[1]
        end
      end
      
      [1, 2, 3, 4, 5].second
      # => 2
      

      或者如果你想继承一个数组。

      class Foo < Array
        def second
          self[1]
        end
      end
      
      [1, 2, 3, 4, 5].include? 2
      # => true
      
      [1, 2, 3, 4, 5].second
      # => 2
      

      如果您还有任何问题,请发表评论,我会更新答案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-18
        • 2012-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多