Ruby Module用于在包含的类中创建具有默认值的类变量

时间:2015-10-30 06:13:30

标签: ruby metaprogramming

我想创建一个ruby模块,当它被一个类包含时,它设置一个具有默认值的类变量,该默认值可以在类定义中被覆盖。我一直在用元编程来扭曲自己,试图让它发挥作用,但我无法做到正确。

我想要的:

class A
  include Nickname
end

A.nickname # => 'A'
A.new.nickname # => 'A'

class B
  include Nickname
  set_nickname "Bubba"
end

B.nickname # => 'Bubba'
B.new.nickname # => 'Bubba'

class BB < B
end

BB.nickname # => 'Bubba'
BB.new.nickname # => 'Bubba'
A.nickname # => 'A'
A.new.nickname # => 'A'

我试过

module Nickname

  def self.included(klass)
    klass.class_eval <<-EOS
      @@nickname = "#{klass.name}"
      def self.nickname
        @@nickname
      end
      def nickname
        @@nickname
      end
      def self.set_nickname(new_nick)
        @@nickname = new_nick
      end
    EOS
  end

end

但是在Nickname模块上设置了昵称,因此包含该模块的每个类都具有相同的昵称(最后设置的是什么)。 原来代码是对的。在7stud指出这一点之后,我回顾了我的工作。似乎因为我在irb进行测试并且先前已经通过其他几种方式定义了Nickname,所以我得到了不良行为。实际上,在irb的新负载下,上述代码的工作方式与我想要的完全一样。对不起。

然后我尝试使用类实例变量

module Nickname

  def self.included(klass)
    klass.send(:define_singleton_method, :nickname) do
      @nickname
    end
    klass.send(:define_method, :nickname) do
      klass.nickname
    end
    klass.send(:define_singleton_method, :set_nickname) do |new_nick|
      @nickname = new_nick
    end
    klass.set_nickname klass.name
  end

end

这几乎有效,但它使用了类实例变量,因此BB.nicknamenil

我在class_eval内尝试了instance_evalself.included的各种组合,我无法全部合作。如果我设法将值存储为包含类中的类变量,我就无法使类能够覆盖该值和/或我无法设置默认值。

修改

故事的道德1,要小心在irb中进行元编程。你失败的实验很可能会破坏你的下一次实验。

故事的道德2是,如果您认为类变量很奇怪,请查看具有继承和instance_variable_set/get的类实例变量。当一个子类继承了超类&#39;类实例变量,该变量仍然附加到子类实例的超类部分,因此您无法通过子类中的@var访问它,并且您无法从访问{{1的超类函数访问它但是,正如7stud所示,您可以从一个自己调用@var的超类函数访问它。

2 个答案:

答案 0 :(得分:2)

  

...但是在昵称模块上设置了昵称......

我没有看到:

module Nickname
  def self.included(klass)
    klass.class_eval %Q{
      @@nickname = #{name} 

      def self.set_nickname(new_nick)
        @@nickname = new_nick
      end
    }
  end

end

class A
  include Nickname
end

p Nickname.class_variables(false)
p A.class_variables(false)


A.set_nickname("Joe")

p Nickname.class_variables(false)
p A.class_variables(false)

--output:--
[]
[:@@nickname]
[]
[:@@nickname]
  每个班级   包含该模块的将具有相同的昵称(无论如何   设置最后)。

不,include不是罪魁祸首:

module Nickname
  def self.included(includer)
    includer.class_eval %Q{
      @@nickname = #{name}

      def self.set_nickname(new_nick)
        @@nickname = new_nick
      end

      def self.nickname
        @@nickname
      end
    }
  end

end

class A
  include Nickname
end

A.set_nickname("Joe")
p A.nickname

class B
  include Nickname
end

B.set_nickname("Sally")
p B.nickname
p A.nickname

--output:--
"Joe"
"Sally"
"Joe"

相反,问题是inheritance

module Nickname
  def self.included(includer)
    includer.class_eval %Q{
      @@nickname = #{name}

      def self.set_nickname(new_nick)
        @@nickname = new_nick
      end

      def self.nickname
        @@nickname
      end
    }
  end

end

class A
  include Nickname
end

A.set_nickname("Joe")
p A.nickname

class B < A
end

B.set_nickname("Sally")
p B.nickname
p A.nickname

--output:--
"Joe"
"Sally"
"Sally"

继承层次结构中的所有类共享@@variable,这是Sergio Tulentsev试图在评论中警告你的内容。你可能不想使用@@variable(没人做)。这是一个class instance变量,它不会遇到这个问题:

module Nickname

  def self.included(includer)
    includer.class_eval do
      @nickname = includer.name  #Instance variables attach themselves to whatever object is self, and class_eval sets self to the receiver, i.e. includer

      define_method(:nickname) do  #Using define_method() with a block makes the includer variable visible inside the nickname() method.
        includer.instance_variable_get(:@nickname)
      end
    end

    includer.singleton_class.class_eval do
      define_method(:nickname) do
        includer.instance_variable_get(:@nickname)
      end
      define_method(:set_nickname) do |new_val|
        includer.instance_variable_set(:@nickname, new_val)
      end
    end

  end

end


class A
  include Nickname
end

p A.nickname
p A.new.nickname

class B
  include Nickname
  set_nickname "Bubba"
end

p B.nickname
p B.new.nickname

class BB < B
end

p BB.nickname
p BB.new.nickname

--output:--
"A"
"A"
"Bubba"
"Bubba"
"Bubba"
"Bubba"

然后你可以在BB中设置类实例变量,它不会影响B的类实例变量:

...
...

class BB < B
  include Nickname
  set_nickname "Bubba Bubba"
end

p BB.nickname
p BB.new.nickname

p B.nickname
p B.new.nickname

--output:--
...
...
"Bubba Bubba"
"Bubba Bubba"
"Bubba"
"Bubba"

答案 1 :(得分:0)

Module#class_variable_set来救援:

▶ module A
▷   def self.included base
▷     base.class_variable_set :@@nickname, 'mudasobwa'
▷     class << base
▷       def nickname # extended class method
▷         class_variable_get :@@nickname
▷       end
▷     end
▷   end
▷ end

▶ class B
▷   include A
▷   def nickname # instance method, might be put in `A`
▷     @@nickname
▷   end
▷ end

▶ B.nickname
#⇒ "mudasobwa"
▶ B.new.nickname
#⇒ "mudasobwa"
▶ B.class_variable_set :@@nickname, 'google'
#⇒ "google"
▶ B.nickname
#⇒ "google"
▶ B.new.nickname
#⇒ "google"

希望它有所帮助。