У меня проблема с модулями - PullRequest
2 голосов
/ 09 февраля 2010

Я пытаюсь определить статическую переменную и методы в модуле, который будет расширен / использован многочисленными классами. Следующий пример демонстрирует:

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

Это не работает, потому что боеприпасы по какой-то причине не знают, как оценивать @@ ammo в контексте класса (я изначально думал, что модуль будет вести себя так же, как включаемый файл). Мне нужно было бы скопировать 'unload' для каждого класса, что я сейчас и делаю, но я хочу высушить его, потому что у меня есть много других методов, чтобы добавить в модуль.

Предложения? Разумным решением было бы оценить 'unload' в контексте класса, а не модуля (но как это сделать в Ruby?)

Спасибо!

Ответы [ 3 ]

4 голосов
/ 09 февраля 2010

переменные класса могут работать странно, и это показывает, что это не так. Каков объем @@ammo? Ammunition или Tank имеет свою собственную копию? Оказывается, модуль @@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}"

Классы могут иметь переменные экземпляра, и их область видимости легче понять. А разделение вашего модуля на методы экземпляра и класса позволяет вам предоставлять функциональность обоим. Этот фрагмент генерирует следующий вывод

Tank unloaded: bullets,shells
Airplane unloaded: bullets,missiles,photon_torpedoes
3 голосов
/ 09 февраля 2010

Ну, во-первых ... действительно хорошая идея объяснить, как точно работают @@ переменные.

@@ переменные - это переменные класса, к которым можно обращаться в контексте экземпляра, например, например:

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

Почему @ также для переменных класса? Помните, что Klass в этом экземпляре класса Class, у него будут свои собственные переменные экземпляра, которые в конце будут переменными класса для экземпляров 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

Этот код, на который указывают другие, не будет работать (учитывая, что нет снарядов, ракет или фоторамок), но я думаю, вы можете понять, как заставить его работать самостоятельно.

0 голосов
/ 09 февраля 2010

Несколько вопросов:

(1) Имя модуля ammunition должно начинаться с заглавной буквы - Ammunition

(2) вы включаете Packagable в свои классы, но я предполагаю, что вы имеете в виду Ammunition?

(3) все ваши переменные - missiles, photon и photon_torpedos не определены, поэтому ваш код фактически не выполняется.

Я предлагаю вам сначала исправить этот код :) Но в качестве исключения, переменные класса @@myvar считаются нет-нет среди большинства Rubyists.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...