【问题标题】:How can I access a superclass class variable from a subclass in Ruby如何从 Ruby 中的子类访问超类类变量
【发布时间】:2020-06-03 21:55:23
【问题描述】:

我正在尝试从子类访问类变量。我知道类变量不是继承的,这回答了为什么代码不起作用的问题,但是我不完全理解如何解决它。

这是我的代码:

Class A
  ...
  class << self 
    def format(a, b)
      @format = a
    end

    def item(a, b)
      @item[a] = b
    end

  end
end

Class B < A
  format 4, 7

  item 7, 12
  ...
end

Class C < B
  item 7, 18
end 

在 irb 会话中运行以下命令

B.format => 4
C.format => nil 

所以了解类变量不是继承的,是否可以制作C.format =&gt; 4 或者我需要这样重构:

Class B < A
  format 4, 7
  item 7, 12
end

Class C < A
  format 4, 7
  item 7, 18
end

我想避免后者的原因是我有很多以相同方式定义的变量(调用函数来设置类变量),我不想在 B 类中复制所有代码和 C,因为一个实例变量不同。

【问题讨论】:

  • 为什么不创建一个模块并将变量(可能是attr_accessors)放在那里?
  • 您的代码中没有类变量。你是说实例变量吗?

标签: ruby class inheritance


【解决方案1】:

我了解类变量不会被继承

这完全是错误的,你还没有真正理解什么是类变量。

Ruby 中的类变量是用@@ sigil 声明的。他们肯定是遗传的:

class A
  @@x= "Hello World"

end

class B < A
  puts @@x # Outputs "Hello World"
end

类变量实际上是在一个类和它的子类之间共享的:

class Animal
  @@greating = "Hello"
  def greet
    "#{@@greating} I'm a #{self.class.name.downcase}"
  end
end

class Cat < Animal
  @@greating = "Meow"
end

class Dog < Animal
  @@greating = "Woof"
end

puts Dog.new.greet # Woof I'm a dog
puts Cat.new.greet # Woof I'm a cat
puts Animal.new.greet # Woof I'm a animal

正如您从示例中看到的那样,这通常会导致意外和不受欢迎的效果。并且类变量不被认为是线程安全的。

您实际设置的内容称为class instance variable - 这只是一个实例变量,只是它的作用域不是 A 的实例。相反,它的作用域是单例类 A,它是 Class 类的实例.

与真正的类变量不同,它们有自己的印记类实例变量不会在类及其子类之间共享,因为它们的作用域是the singleton class。每个子类都有自己的单例类。

class Animal
  @greating = "Hello"

  def self.greeting
    @greating
  end

  def greet
    "#{self.class.greeting} I'm a #{self.class.name.downcase}"
  end
end

class Cat < Animal
  @greating = "Meow"
end

class Dog < Animal
  @greating = "Woof"
end

puts Dog.new.greet # Woof I'm a dog
puts Cat.new.greet # Meow I'm a cat
puts Animal.new.greet # Hello I'm a animal

类实例变量实际上比真正的类变量有用得多。如果你想用类实例变量模拟类变量的继承,你可以通过Ruby提供的Class#inherited回调来实现:

class A
  @x = [self.name]
  class << self
    attr_accessor :x
    def inherited(subclass)
      puts "#{subclass.name} is inheriting from #{self.name}"
      subclass.x = x
      subclass.x.push(subclass.name)
    end
  end
end

class B < A; end # outputs "B is inheriting from A"
class C < B; end # outputs "C is inheriting from B"
puts B.x.inspect # => ['A', 'B']
puts C.x.inspect # => ['A', 'B', 'C']

【讨论】:

    【解决方案2】:

    首先,要定义一个类,您需要使用关键字class 而不是Class

    其次,没有理由向方法A::format 传递它不使用的参数。因此,我将其更改为只有一个参数。

    第三,在定义A之后,当A.item第一次被执行时会抛出一个异常,因为@item还没有被定义。具体来说,@item(与任何其他未定义的实例变量一样)在调用时将返回nil,而nil 没有方法[]。因此,我更改了方法item 将类实例变量(不是类变量)@item 初始化为一个空数组。

    class A
      class << self 
        def format(a)
          @format = a
        end    
        def item(a, b)
          @item = []
          @item[a] = b
        end
      end
    end
    

    class B < A
      format 4
      item 7, 12
    end
    

    class C < B
      item 7, 18
      def self.format
        superclass.instance_variable_get(:@format)
      end
    end
    

    据我了解,您希望方法 C::format 返回 B 的实例变量 @format 的值。

    C.format
      #=> 4 
    

    如果尝试执行B.format,则会引发异常,因为B::format 需要两个参数。如果B.format 是为了返回B 的类实例变量@format 的值,你需要这样写:

    B.instance_variable_get(:@format)
      #=> 4 
    

    您可以为实例变量@format 添加读写访问器,在这种情况下,您的代码会有所简化:

    class A
      class << self
        attr_accessor :format
        def item(a, b)
          @item = []
          @item[a] = b
        end
      end
    end
    

    class B < A
      @format = 4
      item 7, 12
    end
    

    class C < B
      item 7, 18
      def self.format
        superclass.format
      end
    end
    

    C.format
      #=> 4 
    

    【讨论】:

    • 在示例中为class B &lt; A; format = 4; item 7, 12; endformat = 4 这只会设置局部变量 format。您需要明确指定recient self.format = 4 或直接设置实例变量@format = 4
    • 谢谢,@max。初学者的错误。
    猜你喜欢
    • 2022-01-05
    • 2012-04-09
    • 2016-03-08
    • 2017-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-08
    相关资源
    最近更新 更多