【问题标题】:Ruby inheritable class macroRuby 可继承类宏
【发布时间】:2016-08-31 02:37:49
【问题描述】:

假设我想在 Ruby 中创建一个宏。

class Base
  def self.option(name,val)
    options[name] = val
  end

  def self.options
    @options ||= {}
  end
end

class Foo < Base
  option :one, 1
  option :two, 2
end

Foo.options #=> {:one => 1, :two => 2}

好的,很简单。

但是如果我想继承 Foo 呢?

class Bar < Foo
end

Bar.options #=> {}

这太糟糕了。

所以很明显问题是每个类的类实例变量是唯一的,即。 @options inside Bar@options inside Foo 不同。

所以也许是一个类变量?我一直无法找出其中一个的有效用途,让我们试试吧。

# the rest of the code unchanged
class Base
  def self.options
    @@options ||= {}
  end
end

Bar.options #=> {:one => 1, :two => 2}

嘿,成功了! ...不是吗?

class Baz < Foo
  option :three, 3
end

Foo.options #=> {:one => 1, :two => 2, :three => 3}
Bar.options #=> {:one => 1, :two => 2, :three => 3}
Baz.options #=> {:one => 1, :two => 2, :three => 3}

X-|

好的,我一直在谷歌上搜索这个问题,但没有发现任何有用的信息。我尝试了一些尝试阅读超类选项(如果已定义)的变体,但无济于事。我想我不妨问问。

你们中有人知道怎么做吗?还是根本不可能……

【问题讨论】:

    标签: ruby inheritance macros


    【解决方案1】:

    我相信这是你需要的:

    class Base
      def self.option(name, val)
        options[name] = val
      end
    
      def self.options
        @options ||= if self.superclass.respond_to?(:options)
                       self.superclass.options.dup
                     else
                       {}
                     end
      end
    end
    
    class Foo < Base
      option :one, 1
      option :two, 2
    end
    
    class Bar < Foo
      option :three, 3
    end
    
    class Hello < Bar
      option :world, 4
    end
    
    puts Foo.options # {:one=>1, :two=>2}
    puts Bar.options # {:one=>1, :two=>2, :three=>3}
    puts Hello.options #{:one=>1, :two=>2, :three=>3, :world=>4}
    

    【讨论】:

    • 这里很接近,但.dup 调用意味着您不知道超类在事后所做的任何更改。
    • 问题没有提到观察原始宏。
    • 是的,在这种情况下,dup 很好。这是一个很好的干净解决方案。我尝试了类似的方法,但没有成功,但老实说,我不知道为什么。我猜一定是打错了。无论如何,谢谢你为我理顺它:)
    【解决方案2】:

    如果您想要一个足够通用以适应在派生子类后对基类所做的更改:

    class Base
      def self.option(name,val)
        @options ||= { }
    
        @options[name] = val
      end
    
      def self.options
        @options ||= { }
    
        if (self == Base)
          @options
        else
          @options.merge(self.superclass.options)
        end
      end
    end
    
    class Foo < Base
      option :one, 1
      option :two, 2
    end
    
    class SubFoo < Foo
      option :three, 3
    end
    
    Foo.options
    #=> {:one => 1, :two => 2}
    SubFoo.options
    #=> {:three=>3, :one=>1, :two=>2}
    
    class Foo
      option :four, 4
    end
    
    Foo.options
    #=> {:one=>1, :two=>2, :four=>4}
    SubFoo.options
    #=> {:three=>3, :one=>1, :two=>2, :four=>4}
    

    【讨论】:

      【解决方案3】:
      class Base
        def self.option(name,val)
          options[name] = val
        end
      
        def self.options
          @options ||= {}
        end
      end
      
      class Foo < Base    
        option :one, 1
        option :two, 2
      end
      
      class Bar < Foo
        @options = superclass.options.clone
        option :three, 3
      end
      
      class Baz < Foo
        @options = superclass.options.clone
        option :four, 4
      end
      
      puts Foo.options #=> {:one => 1, :two => 2}
      puts Bar.options #=> {:one => 1, :two => 2, :three => 3}
      puts Baz.options #=> {:one => 1, :two => 2, :four  => 4}
      

      这是我能想到的唯一可行的方法,您只需从超类中克隆@options。这样,每个类都有自己的实例变量。

      【讨论】:

      • 我尝试了几种变体,但都无法正常工作...您对此进行了测试吗?
      • 好的,我猜我的测试出了点问题,因为我看到你的 repl 有效。谢谢:)
      猜你喜欢
      • 2010-11-08
      • 2011-04-16
      • 2020-08-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-13
      相关资源
      最近更新 更多