【问题标题】:A problem I'm having with modules我遇到的模块问题
【发布时间】:2010-02-09 19:15:41
【问题描述】:

我正在尝试在一个模块中定义一个静态变量和方法,这些变量和方法将被众多类扩展/使用。下面的例子演示:

module Ammunition
  def self.included(base)    
    base.class_eval("@@ammo = [bullets]") 
  end

  def unload
    p @@ammo #<-- doesn't work
  end  
end

class Tank
  include Ammunition
  @@a += [shells]
end

class Airplane
  include Ammunition  
  @@a += [missiles, photon_torpedoes]
end

Tank.new.unload
Airplane.new.unload

这不起作用,因为 ammunition 不知道如何在类的上下文中评估 @@ammo 出于某种原因(我最初认为该模块的行为就像一个包含文件)。我必须将“卸载”复制到每个类,我现在正在这样做,但我想把它干掉 b/c 我还有许多其他方法可以添加到模块中。

建议?合理的解决方案是在类而不是模块的上下文中评估“卸载”(但是如何在 Ruby 中做到这一点?)

谢谢!

【问题讨论】:

  • 1.为什么使用 Ruby on Rails 标签? 2. 你的模块被称为ammunition (Ammunition?),但你后来被称为include Packagable - 这根本不在这里。 3. 你的代码试图做什么并不是很清楚。描述有所帮助,但并非完全有帮助。
  • 感谢 Squeegy - 您设法回答了我令人费解且措辞/编辑不佳的问题(抱歉没有更好地措辞)!我的实际问题是我试图编写一个用于 json 的模块,该模块将一堆模型与多态关联捆绑在一起。我最终按照你的方式设置了 attr_reader。

标签: ruby-on-rails ruby


【解决方案1】:

类变量可以奇怪地工作,这种用法表明了这一点。 @@ammo的范围是什么? AmmunitionTank 有它自己的副本吗?原来@@ammo是模块作用域的,包含它的类可以简单地访问它。

module Ammunition
  def self.included(base)    
    base.class_eval do
      puts "@@ammo was: #{defined?(@@ammo) ? @@ammo.join(',') : 'nil'}"
      @@ammo = ['bullets']
      puts "@@ammo is now: #{@@ammo}"
      puts '---'
    end
  end

  def unload
    @@ammo
  end  
end

class Tank
  include Ammunition
  @@ammo += ['shells']
end

class Airplane
  include Ammunition  
  @@ammo += ['missiles', 'photon_torpedoes']
end

puts "Tank unloaded: #{Tank.new.unload.join(', ')}"
puts "Airplane unloaded: #{Airplane.new.unload.join(', ')}"

这会产生:

@@ammo was: nil
@@ammo is now: bullets
---
@@ammo was: bullets,shells
@@ammo is now: bullets
---
Tank unloaded: bullets, missiles, photon_torpedoes
Airplane unloaded: bullets, missiles, photon_torpedoes

Tank 包含模块时,它会将@@ammo 从nil 设置为一个包含项目符号的数组。当Airplane 包含该模块时,它会覆盖我们刚刚设置的弹药值。


这是你想做的事

module Ammunition
  def self.included(base)    
    base.class_eval do
      include Ammunition::InstanceMethods
      extend  Ammunition::ClassMethods
      @ammo = ['bullets']
    end
  end

  module ClassMethods
    def ammo
      @ammo
    end
  end

  module InstanceMethods
    def unload
      self.class.ammo.join(',')
    end
  end
end

class Tank
  include Ammunition
  @ammo += ['shells']
end

class Airplane
  include Ammunition  
  @ammo += ['missiles', 'photon_torpedoes']
end

puts "Tank unloaded: #{Tank.new.unload}"
puts "Airplane unloaded: #{Airplane.new.unload}"

类可以有实例变量,它们的作用域更容易理解。并且将您的模块分成实例和类方法允许您为两者提供功能。这个 sn -p 生成如下输出

Tank unloaded: bullets,shells
Airplane unloaded: bullets,missiles,photon_torpedoes

【讨论】:

  • 绝对是要走的路——应该使用 attr_reader :ammo 作为 ammo getter 方法,而不是手动定义它。
  • 感谢 Squeegy - 您设法回答了我令人费解且措辞/编辑不佳的问题(抱歉没有更好地措辞)!我的实际问题是我试图编写一个用于 json 的模块,该模块将一堆模型与多态关联捆绑在一起。我最终按照你的方式设置了 attr_reader。
  • 很高兴为您提供帮助。我没有使用attr_reader 的原因是因为它通常是在类范围内定义的,但会影响实例范围内的实例变量。通过模块从extend 执行此操作对类执行相同操作。但由于它不常用这种方式,它可能会给你关于它实际作用域的变量的错误想法。所以我猜只是风格偏好。
【解决方案2】:

嗯,首先...解释一下@@ 变量是如何工作的确实是个好主意。

@@ 变量是可以在实例上下文中访问的类变量,例如:

class Klass

  def my_klass_variable=(str)
    # self here points to an instance of Klass
    @@my_klass_variable = str
  end

  def my_klass_variable
    @@my_klass_variable
  end

end

Klass.new.my_klass_variable = "Say whaat?"
# Note this is a different instance
Klass.new.my_klass_variable # => "Say whaat?" 

但是这种类型的变量也会导致以下结果:

class OtherKlass < Klass; end

Klass.new.my_klass_variable = "Howdy"
# Note this is a different instance, and from the child class
OtherKlass.new.my_klass_variable # => "Howdy"

确实是疯狂的行为。创建类变量的另一种方法是在以self. 开头的方法上定义实例变量。例如:

class Klass 
  def self.my_class_method
    @class_var = "This is a class var"
  end
end

为什么@ 也用于类变量?请记住,这里的KlassClass 类的instance,它将有自己的实例变量,最后将是Klass 实例的类变量。

Klass.class # => Class
Klass.instance_of?(Class) # => true
k = Klass.new
k.class # => Klass
k.instance_of?(Klass) # => true

这对于类变量更安全(因为它们将拥有变量的一份副本,而不是与子类共享的),并且在使用示例时会表现得像您期望的那样:

module Ammunition

  def self.included(base)    
    base.class_eval do
      @ammo = [bullets] # where bullets come from any way?
    end
  end

  def self.unload
    p @ammo
  end

end

class Tank
  include Ammunition # Probably you meant that instead of Packagable
  @ammo += [shells] # I think you meant @ammo instead of @a
end

class Airplane
  include Ammunition # Probably you meant that instead of Packagable
  @ammo += [missiles, photon_torpedoes] # I think you meant @ammo instead of @a
end

其他人指出的这段代码不起作用(鉴于没有炮弹、导弹或photo_torpedoes),但我认为您可以自己弄清楚如何使其工作。

【讨论】:

    【解决方案3】:

    几个问题:

    (1)模块名称ammunition必须以大写开头——Ammunition

    (2) 您将Packagable 包含在您的课程中,但我认为您的意思是Ammunition

    (3) 您的所有变量 - missilesphotonphoton_torpedos 均未定义,因此您的代码实际上并未运行。

    我建议你先修复这段代码 :) 但顺便说一句,类变量 @@myvar 在大多数 Ruby 爱好者中被认为是禁忌。

    【讨论】:

      猜你喜欢
      • 2010-12-29
      • 1970-01-01
      • 2021-06-21
      • 2016-05-12
      • 2012-01-07
      • 2022-01-26
      • 2011-08-06
      • 2020-06-28
      相关资源
      最近更新 更多