Ruby 中的子类实例变量更改超类实例变量

Subclass instance variables changing superclass instance variables in Ruby

在这两个帖子中:

  1. Instance Variables Inheritance

  2. Can Ruby subclass instance variables _overwrite_ the superclass's (same name)?

人们在写关于如何在所有 OOP 语言中超级class 和派生 classes 没有单独的对象,并且当您创建派生 class 的实例时],它也是superclass的一个实例。有一个对象,它同时是 class 个。

嗯,问题是我不明白?一个对象怎么能同时是 class 呢?当然,如果实例变量使用相同的名称,它们怎么可能相同?

我的意思是我从 superclasses 那里得到了 subclasses get 方法,但是我不明白他们是如何在那里共享实例变量的?

我至少翻阅了四本 Ruby 书,但我发现的全部内容是实例变量不通过继承链共享?

有人可以简要解释一下子class中的"instance variables"实际上是如何以及为什么存储在超级class中的吗?

让我们考虑真实的事物,而不是 classes。

假设你有宠物的概念,假设所有的宠物都有名字。现在假设您有 PetDog 的概念,就像所有宠物一样,它有一个名字,但也有一个 "breed".

如您所见,将 PetDog 视为宠物(它的超级class)和宠物狗(宠物的子class)是完全合理的

从数学上讲,您可以想到 class 一组具有某些相似特征的所有对象,然后是 subclass "surrounds" superclass 和更多特点。

class Food
  attr_accessor :name
end

class Fruit < Food
  attr_accessor :ripeness
end

banana = Fruit.new
banana.ripeness = 0 # very green banana
banana.name = "banana"  

steak = Food.new
steak.ripeness = 3 # ERROR... the base class Food does not know about "ripeness"

一个真实的(有效的)例子怎么样?让我们使用 react.rb DSL 来定义时钟和闹钟 UI 组件

时钟组件 class 是 React::Component::Base 的子 class,因此得到很多 "state" 是基础 class 的一部分。它知道如何呈现 html,它知道当事情更新时如何做出反应等。基础 class 的所有部分,以及控制它并使其工作的所有数据都留在基础中class.

此外,Clock i 有一些特殊的属性,即它在@clock 变量中跟踪当前时间。它还每秒更新一次。

然后我们做一个子class,叫做Alarm。

因为闹钟是时钟的子class,所以它具有内部@clock 变量(实际上是时钟的一部分 class),并且像任何其他时钟一样,闹钟会在每个时间更新自己第二。 这个很重要!注意,初始化@clock变量的是Clockclass,也是使用时钟更新的Clockclass(重新显示)本身。当 Alarm 继承自 Clock 时,@clock 变量必须保留为时钟的一部分,以便其正常运行。

但除此之外,Alarm 还使用@clock 变量来实现自己的目的,即检查并查看 "times up",当它启动时,它很好地停止时钟 运行ning(注意即使在显示警报后,时钟仍会保持 运行ning 但闹钟不会。

希望这对您有所帮助 - 请务必运行示例!

<div id="container"></div>
<script type="text/ruby">
class Clock < React::Component::Base
  # a component that displays the time
  after_mount do 
    @clock = every(1) { force_update! }
  end
  def render
    "#{Time.now}"
  end
end

class Alarm < Clock
  # a kind of clock that notifies its caller when time is up!
  param :at, type: Time
  param :notify, type: Proc
  before_update do  
    # before_update is a feature of React::Component::Base
    # the following code is executed before each update of the Alarm
    if Time.now > params.at
      params.notify
      @clock.stop
    end
  end
  def render
    "Alarm will go off in #{(params.at-Time.now).to_i} seconds"
  end
end

Element['#container'].render do
  div do
    # This is react.rb's way of creating a new instance of Clock
    # Its shorthand for doing a Clock.new() and a bunch of other stuff
    Clock()  
    br
    Alarm(
     at: Time.now+10.seconds, 
     notify: -> { alert ('beep beep beep') }
    )
  end
end

</script>


<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<script src="https://rawgit.com/reactive-ruby/inline-reactive-ruby/master/inline-reactive-ruby.js"></script>

这里要理解的关键是:实例变量与当前对象(或实例)相关联,而方法与classes.

这样做的原因是您不希望为创建的每个对象都创建新方法,如果它们将与该 class 的所有其他对象具有相同的主体。

另一方面,每个对象的实例变量必须是单独的,否则在改变值时你将得到一个完整的混搭。


考虑到这一点,让我们考虑以下示例:

class Foo
  def foo
    @var = :foo
  end
end

class Bar < Foo
  def bar
    @var = :bar
  end
end

让我们探索创建新对象时会发生什么:

baz = Bar.new

目前,baz 没有任何实例变量。它只有一个指针,上面写着 "Bar is my class".

baz.class # => Bar

FooBaz class 没有任何诸如 @var 之类的东西与之关联。所以目前,没有人知道 @var


当您在 baz 上调用方法时,会在 Bar 祖先链 :

中搜索方法定义
baz.class.ancestors # => [Bar, Foo, Object, Kernel, BasicObject]

这里可以注意几点:首先,那里的东西是按照继承顺序出现的。其次,Foo隐式继承自Object。第三,模块也可以是链的一部分(如Kernel)。

baz.bar # => :bar

将在链中找到第一个有方法 #bar 的东西并调用它。该值被分配给 @var 实例变量,它与对象 baz 关联,而不是 classes FooBar.

baz.foo # => :foo

现在将使用 Foo 中的方法定义。但同样,@var 正在 baz 中更改。


一些相关的东西供以后阅读 (为简单起见,上面的一些内容并不完全准确):

  • 单例classes
  • class 变量
  • including/prepending 模块
  • Class.superclass # => Module
  • BasicObject#method_missing