开发者

A problem I'm having with modules

开发者 https://www.devze.com 2022-12-19 19:28 出处:网络
I\'m trying to define a static variable and methods in a module that will be extended/used by numerous classes. The following example demonstrates:

I'm trying to define a static variable and methods in a module that will be extended/used by numerous classes. The following example demonstrates:

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

This doesn't work because ammunition doesn't know how to evaluate @@ammo in the context of the class for some reason (I original t开发者_开发技巧hought the module would behave just like an include file). I would have to copy 'unload' to each class, which I'm doing right now, but I want to DRY it up b/c I have many other methods to add to the module.

Suggestions? The reasonable solution would be to evaluate 'unload' in the context of the class and not the module (but how to do this in Ruby?)

Thanks!


class variables can work strangely, and this use shows that off. What is the scope of @@ammo? Ammunition or does Tank have its own copy of it? It turns out that @@ammo is scoped by the module, and the classes that include it can simply access it.

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(', ')}"

This produces:

@@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

When Tank includes the module, it sets @@ammo from nil to an array with bullets in it. When Airplane includes the module, it overwrites the ammo value we just set.


Here is what you want to do

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}"

Classes can have instance variables, and their scope is easier to understand. And separating your module into instance and class methods allow you to provide functionality to both. This snippet generates the following output

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


Well, first of all... it's a really good idea to explain how @@ variables work exactly.

@@ variables are class variables that can be accessed on the instance context, say for example:

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?" 

However this type of variables will incur also in the following result:

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"

Crazy behavior indeed. Another way to create Class variables, is defining instance variables on a method that starts with self.. For example:

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

Why a @ for class variables as well? Remember that Klass in this is an instance of the Class class, this will have its own instance variables, that at the end will be class variables for instances of Klass.

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

This is more safe for class variables (as they will have one copy of the variable, and not a shared one with child classes as well), and will behave as you are expecting to behave when using your example:

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

This code as pointed by others won't work (given there is no shells, missiles nor photo_torpedoes), but I think you can figure it out how to make it work by yourself.


A few issues:

(1) The module name ammunition must start with a capital -- Ammunition

(2) you're including Packagable into your classes but i assume you mean Ammunition ?

(3) all of your variables - missiles, photon and photon_torpedos are undefined, so your code does not actually run.

I suggest you first fix this code :) But as an aside, class variables @@myvar are considered a no-no among most Rubyists.

0

精彩评论

暂无评论...
验证码 换一张
取 消