まったくの初心者の質問で申し訳ありませんが、@game_score が常に nil なのはなぜですか?
#bowling.rb
class Bowling
@game_score = 0
def hit(pins)
@game_score = @game_score + pins
end
def score
@game_score
end
end
まったくの初心者の質問で申し訳ありませんが、@game_score が常に nil なのはなぜですか?
#bowling.rb
class Bowling
@game_score = 0
def hit(pins)
@game_score = @game_score + pins
end
def score
@game_score
end
end
コードを見ていきましょう。
#bowling.rb
class Bowling
@game_score = 0 # (1)
この時点 (1) では、まだクラス Bowling
内にいます。覚えておいてください: クラスは他のオブジェクトと同じように単なるオブジェクトです。したがって、この時点でクラス object0
のインスタンス変数に代入しています。@game_score
Bowling
def hit(pins)
@game_score = @game_score + pins # (2)
(2) では、クラスのインスタンス メソッドの内部にいます。つまり、これは のインスタンスBowling
に属するメソッドです。したがって、インスタンス変数は、クラス自体ではなく、クラスのインスタンスに属します。Bowling
@game_score
Bowling
このインスタンス変数は何にも初期化されないため(nil
Ruby では、初期化されていない変数は常に に評価されますnil
)、これは に評価され@game_score = nil + pins
、メソッドnil
がないため、例外が発生します。#+
NoMethodError
end
def score
@game_score # (3)
ここ (3) では、再びクラスのインスタンス メソッド内にいます。これは、上で概説した理由により、Bowling
常に と評価されます。初期化されないため、 と評価されます。nil
@game_score
nil
end
end
Ruby のリフレクション機能を使用して、何が起こっているかを確認できます。
p Bowling.instance_variable_get(:@game_score) # => 0
b = Bowling.new
p b.instance_variable_get(:@game_score) # => nil
次に、インスタンス変数に値を挿入しましょう。
b.instance_variable_set(:@game_score, 1)
p b.score # => 1
b.hit(3)
p b.score # => 4
したがって、すべてが正常に機能することがわかります。あとは、インスタンス変数が確実に初期化されるようにする方法を理解するだけです。
そのためには、初期化メソッドを記述する必要があります。奇妙なことに、初期化メソッドは実際にはと呼ばれるプライベートインスタンス メソッドinitialize
です。initialize
(なぜクラスメソッドではなくインスタンスメソッドなのかというと、実はとても単純な理由です。Rubyはオブジェクト生成をメモリ割り当てとオブジェクト初期化の2段階に分けています。メモリ割り当ては呼び出されたクラスメソッドによって行われalloc
、オブジェクトの初期化はインスタンスによって行われます。 (Objective-C プログラマーはこれに気付くでしょう。) なぜクラスメソッドなのinitialize
かという理由alloc
は、実行のこの時点ではまだインスタンスがないからです。initialize
インスタンスメソッドであるということは、オブジェクトの初期化が明らかにオブジェクトごとであることです。new
便宜上、 と の両方を呼び出す標準ファクトリ クラス メソッドがalloc
ありinitialize
ます。)
class Bowling
def initialize
@game_score = 0
end
end
これをテストしましょう:
c = Bowling.new
p c.score # => 0
c.hit(2)
p c.score # => 2
ところで: ちょっとした Ruby スタイルのヒント: インデントは 1 つのタブではなく 2 つのスペースです。そして、あなたのhit
方法はより慣用的に@game_score += pins
.
あなたが持っていないので
def initialize
@game_score = 0
end
クラス定義の割り当ては、あなたが思っていることをしていません。hit
呼び出されたときに、に追加することはできませんnil
。
あなたが今、何が起こったのか尋ねたら@game_score
?、ええと、 Class は objectであり、Object は classであることを常に覚えておいてください。
Ruby クラスがこの禅のような「本物の」存在を持っている方法は、とてもクールです。Ruby には名前付きのクラスが正確に存在するわけではありません。むしろ、クラス名は class のオブジェクトへの参照ですClass
。@game_score
インスタンス メソッドの外側に代入することにより、クラス オブジェクトの属性であるクラス インスタンス変数Bowling
を作成しました。これは、クラスのインスタンスですClass
。一般に、これらのオブジェクトはあまり役に立ちません。(Chapter 1, The Ruby Way、Hal Fulton を参照してください。)
@game_score
そこで定義されているのは、クラスインスタンス変数と呼ばれるものです。これは、シングルトンクラスオブジェクト用に定義された変数です。
class << Bowling
attr_accessor :game_score
end
Bowling.game_score #=> 0
これは、インスタンスオブジェクトに対して定義された通常のインスタンス変数とは異なることがわかるためです。
@game_scoreがここでゼロの値を取得することはありません-次のように、initialize内に配置する必要があります
def initialize @game_score = 0 end