【Ruby入門説明書】インスタンスと変数について説明
クラスは設計図です。
そして、インスタンスは設計図から作られた実態です。
+メソッドやto_sメソッドなどが定義されたIntegerクラスがあり、プログラム内でi = 1などとしたときにiという実態が生成されているわけです。
そんなクラスとインスタンスの関係、インスタンスと変数との関係を詳しく解説していきます。
なお、後半は少し踏み込んだ内容になっていますので、軽く読んで難しければ、しばらく学習した後で戻ってきていただいても良いかもしれません。
むしろ、その方がより理解が深まることでしょう。
Rubyのクラスとインスタンス
インスタンスというのは、クラスという設計図をもとに作られた「もの(オブジェクト)」です。
Rubyはオブジェクト指向言語ですので、オブジェクトを中心にして処理が進みます。
つまり、Rubyプログラムはほとんどの場合、インスタンスが中心となって動いているのです。
現実世界でも、設計図や仕様書は存在しますが、実際に使っているのは、それらから作られた「もの」だというのは、分かると思います。
反面、クラスは設計図として「もの」を作った後に使われることはないのでしょうか?
もちろん、同じ「もの」を作るのに設計図は必須ですので、量産するにはかけがえのないものです。
Inst00-1.rb
class Test
@@serial = 0
def initialize
@name = “Test” + @@serial.to_s
@@serial += 1
end
def get_name
return @name
end
end
test_a = Test.new # ……(1)
test_b = Test.new
test_c = Test.new
puts(test_a.get_name)
puts(test_b.get_name)
puts(test_c.get_name)
Inst00-1.rbは、インスタンスを生成するたびにクラス変数@@serialに格納してあるシリアル番号を+1して@nameに付与するクラスです。
クラス変数はクラスに固有の変数ですので、Testクラスから生成されるインスタンスのインスタンス変数@nameに付与されるシリアル番号が+1されたものになるという仕組みになっています。
現実世界でも量産品にシリアル番号をつけることはありますが、それと同様なことが、クラスとインスタンスでも可能だということです。
クラスもオブジェクト
じつは、設計図であるクラスが、インスタンスを生成するとき以外に利用されることがないかというと、そういうわけでもありません。
クラスも「設計図」という「もの(オブジェクト)」ですので、設計図なりに値を参照されたりすることはあるのです。
現実世界であれば、購入時にカタログや仕様書を見て検討したり、修理やメンテナンスでは、設計図や仕様書をもとに正しい設定になっているかを確認したりするでしょう。それと同様に、クラスというオブジェクトを利用する場合もあります。
Rubyプログラムでは、「クラスオブジェクト」としてメソッドを使うことがあります。
具体的には、Inst00-1.rbの(1)のように、インスタンスを生成するときのnewメソッドは、すべてのクラスで「クラス名.new」の形で利用します。
これはつまり、Rubyでは、クラスもオブジェクトと解釈されていることです。
また、その他の代表的な例としては、Timeクラスでの現在時間の取得もクラスオブジェクトからnowメソッドを利用しています。
puts(Time.now)
クラスとインスタンスの関係
つまり、クラスとインスタンスは、現実世界の「設計図」と「もの」の関係と同じように、お互いオブジェクトとして密接な関係にあります。
むしろ、Rubyのクラスにおいては、インスタンスを作る予定のないクラスもありますので、よりクラスをオブジェクトとして扱う色が濃いと言えるでしょう。
「クラス=設計図」と「インスタンス=もの」という理解が強くなると、インスタンスを作る以外ではクラスが使われないようなイメージが生まれます。そうなると、Rubyの理解の妨げになってしまうのです。
Rubyのクラスとインスタンスは、インスタンスがクラスから生成されるということ以外、オブジェクトとしてはほぼ同等の関係だと思った方が良いでしょう。
Inst00-1.rbを改造して、使い方も含めて動きを見てみましょう。
Inst00-2.rb
class Test
@@serial = 0
attr_reader:name # …….(1)
def initialize
@name = “Test” + @@serial.to_s
@@serial += 1
end
# def get_name # ……….(2)
# return @name
# end
end
test_a = Test.new
test_b = Test.new
test_c = Test.new
# puts(test_a.get_name) # ..(3)
# puts(test_b.get_name)
# puts(test_c.get_name)
puts(test_a.name)
puts(test_b.name)
puts(test_c.name)
(1)で参照専用のアクセサを追加し、(2)のメソッドは不要ですので削除、(3)にあるメソッドでのアクセスを削除して、アクセサでのアクセスへと変更しています。
attr_writerやattr_accessorでも同じ記述で代入専用や参照と代入が可能なアクセサが設定でき、代入する場合は、以下のように単純な記述で行うことができます。
test_a.name = 10
このように、アクセサを使うことで、インスタンス変数へのアクセスをシンプルな記述で表現することが可能になります。
クラスとインスタンスと変数とメソッド
ここからは、少し深い内容の説明をしていこうと思います。
といっても、ここまでに説明した内容で説明し切れていない部分を穴埋めする内容です。つまり、これまでの内容をより深く理解するという意味での深さですので、安心してください。ただし、サンプルプログラムも長くなっていますし、説明も概念的になっていますので、少し難しいと感じたのであれば、あとで読み直すなどしても構いません。自分のペースで進めていきましょう。
Inst01-1.rb
# 計算機クラス
class Calc
puts(“計算機クラスの試作”)
@@memory = 0 # ………………………(1)
# 初期化
def initialize(name = “calc”)
@name = name
cl
end
# 足し算
def inc(val)
temp = @result
@result += val
disp(“inc”, temp.to_s, val.to_s)
end
# 引き算
def dec(val)
temp = @result
@result -= val
disp(“dec”, temp.to_s, val.to_s)
end
# 初期化
def cl
temp = @result
@result = 0
disp(“Init”, temp.to_s, “0”)
end
# 結果表示
def disp(method, last_ret = “”, val = “”, result = @result)
puts(“#{@name}.#{method}(#{val}): #{last_ret} –> #{result}”)
end
private :disp # dispをprivateにする
# メモリー機能の追加
# メモリーを足す # …………………..(2)
def inc_mem
print(“[inc_mem]”)
inc(@@memory)
end
# メモリーを引く # …………………..(3)
def dec_mem
print(“[dec_mem]”)
dec(@@memory)
end
# メモリーを設定 # …………………..(4)
def set_mem
temp = @@memory
@@memory = @result
disp(“set_mem”, temp.to_s, @result, @@memory.to_s)
end
# メモリーをクリア # …………………(5)
def cl_mem
temp = @@memory
@@memory = 0
disp(“cl_mem”, temp.to_s, “”, @@memory.to_s)
end
# 計算機能の追加
@self_ret = 0 # ………………………(6)
# 足し算 # …………………………..(7)
def self.inc(val)
@self_ret += val
return @self_ret
end
# 引き算 # …………………………..(8)
def self.dec(val)
@self_ret -= val
return @self_ret
end
# 初期化 # …………………………..(9)
def self.cl
@self_ret = 0
end
# 結果出力 # …………………………(10)
def self.disp
return @self_ret
end
end
# 計算機インスタンスを生成
calc1 = Calc.new(“calc1”)
calc2 = Calc.new(“calc2”)
# calc2にのみ、かけ算メソッドを追加
# かけ算
def calc2.mul(val) # …………………..(11)
temp = @result
@result *= val
disp(“mul”, temp.to_s, val.to_s)
end
# 計算
calc1.inc(5)
calc1.set_mem # ……………………….(12)
calc1.dec(3)
puts
calc2.inc_mem # ……………………….(13)
calc2.cl_mem # ………………………..(14)
calc2.mul(2) # ………………………..(15)
puts
calc1.dec_mem # ……………………….(16)
# calc1.mul(2) # ………………………(17)
puts
#Calcクラスの計算機能を利用
puts(“#Calcクラスの計算機能を利用”)
puts(“Calc.inc(7) : #{Calc.disp} + 7 = #{Calc.inc(7)}”) # …..(18)
puts(“Calc.dec(5) : #{Calc.disp} – 5 = #{Calc.dec(5)}”) # …..(19)
puts(“Calc.disp : #{Calc.disp}”) # ……………………..(20)
(結果)
[bash]計算機クラスの試作
calc1.Init(0): –> 0
calc2.Init(0): –> 0
calc1.inc(5): 0 –> 5
calc1.set_mem(5): 0 –> 5
calc1.dec(3): 5 –> 2 [inc_mem]calc2.inc(5): 0 –> 5
calc2.cl_mem (): 5 –> 0
calc2.mul (2): 5 –> 10 [dec_mem]calc1.dec(0): 2 –> 2
#Calcクラスの計算機能を利用
Calc.inc(7) : 0 + 7 = 7
Calc.dec(5) : 7 – 5 = 2
Calc.disp : 2
ベースである計算機クラスに、メモリー機能(M+、M-、MRといったボタンで表現されている機能です)とかけ算機能、純粋な計算だけを行う機能を追加しています。
追加機能それぞれについて、変数とメソッドの動きがポイントとなりますので、詳しく説明していきましょう。
変数
ここで追加したのは、ただのメモリー機能ではありません。クラス変数を使った、複数のインスタンスで共有できるメモリー機能なのです。
クラス変数を使っているため、calc1インスタンスとcalc2インスタンスの@@memoryは共通のものになっています。そのため、calc1でセット(Inst01-1.rbの(7))された値をcalc2で使う(Inst01-1.rbの(18))ことができます。
また、計算機能追加用に、インスタンスには影響されない変数とメソッドが追加されています。これら(6)から(10)の変数とメソッドは、インスタンスからはアクセスできず、クラスからのみ利用が可能です。
つまり、この改造によって、このクラスは4種類の変数を使っていることになります。
以下の表で整理しておきましょう。
種別 | 説明 | 文法 | Inst01-1.rb上の変数名 |
---|---|---|---|
ローカル変数 | それぞれのメソッドの中だけで存在しており、メソッドの処理が終了すると失われてしまう変数です。そのため、incメソッド実行後に他のメソッドなどでtempを参照しようとしても、エラーになります。 | 通常の変数通り | incメソッドやdecメソッドにある、変数temp |
インスタンス変数 | インスタンスに属する変数で、インスタンスの中からであれば、どのメソッドからでも参照・変更できます。 インスタンスごとに作られる変数ですので、インスタンスごとに違う値を保持しています。 インスタンス生成後、最初に代入された時点で生成されますが、メソッド内でしか生成できませんので、注意が必要です。 |
変数名の前に「@」 | @result |
クラス変数 | クラスで共通の変数として生成されますので、同じクラスから生成されたインスタンスすべてで共通の値を持つことになります | 変数名の前に「@@」 | @@memory |
クラスインスタンス変数 | クラスメソッド(後述)からのみアクセスできる変数で、メソッド内ではなく、クラス定義内に定義しなければいけません。クラスが定義されると同時に生成され、クラス変数同様、1つのクラスで共通の値を持っています | 変数名の前に「@」 | @self_ret |
メソッド
Inst01-1.rbの(16)で、calc2インスタンスにだけ、かけ算を行えるmulメソッドを追加しています。
calc2インスタンスにだけ追加していますので、calc1で使用しようとするとエラーが発生します。そのため、(17)はコメントアウトしているわけです。
変数のときと同じように、これで計算機クラスは2種類のメソッドを持っていることになります。それぞれについて、まとめておきます。
種別 | 説明 | 文法 | Inst01-1.rb上のメソッド名 |
---|---|---|---|
インスタンスメソッド | クラス内で定義されているが、インスタンスからしか利用できないメソッドです。 | 通常のメソッド通り | incメソッド、decメソッドなど |
特異メソッド | クラス内で定義した場合は、クラスオブジェクトからのみ使用できるクラスメソッドになります。 クラス外で定義した場合は、特定のインスタンスに後からメソッドを定義することができます |
メソッド名の前にオブジェクトを追加します | mulメソッド |
以上が、Inst01-1.rbで使用されているメソッドです。
インスタンスメソッドについては、特筆すべきことはありません。Rubyで作るメソッドのほとんどは、インスタンスメソッドになりますので、一般的にメソッドと言えばインスタンスメソッドのことを指します。
Inst01-1.rbのポイントは、この「特異メソッド」です。
特異メソッドを活用することで、メソッドの追加や、newメソッドやTimeクラスのnowメソッドのような、「インスタンスを不要とするメソッド」であるクラスメソッドを作ることができるのです。
もう少し詳しく説明するために、特異メソッドの文法を紹介しておきましょう。
def オブジェクト名.メソッド名
処理
end
基本的には通常のメソッド定義と違いはありませんが、メソッド名の前にこのオブジェクトを利用するオブジェクトを明示しておく点が違っています。
この明示するオブジェクトによって、その動きが大きく違っていますので、それぞれ説明しましょう。
メソッドの追加
オブジェクトとして、インスタンスを記載することで、そのインスタンスにメソッドを追加することができます。
クラスは、カプセル化によって閉じた世界になっていますので、あまりこの処理は推奨されませんが、自分が必要とする処理がない場合や、あっても細かな調整が必要な場合などには、プログラム全体の可読性を考慮して利用するのも悪くはありません。
ただし、頻繁に使いすぎると、可読性を極めて悪化させます(既存のメソッドなのかどうかの区別かつかない、など)。また、なにより再利用するときに、「クラスを再利用しようとしたら、じつは特異メソッドだった」ということが発生して再利用性も損ないますので、使いすぎには注意が必要です。
クラスメソッド
オブジェクトの部分にクラスを記載しているのが、クラスメソッドです。
Inst01-1.rbでは、selfになっていますが、特殊変数selfは、現在、どのオブジェクトの配下にあるかを保持している変数ですので、selfにはCalcクラスが格納されているわけです。
ほとんどの場合はクラス定義の中で定義されており、インスタンスがなくても機能を提供できるクラスを定義することができます。
インスタンス不要でも利用できるメソッドを提供する意味はいろいろとありますが、以下のような理由が考えられます。
・値を1つ取得するだけの処理(Timeクラスのnowメソッドなど)
・その瞬間にだけ必要で、その後利用することがほとんどない処理(Inst01-1.rbの計算処理など)
・インスタンスを生成する前段階で必要な処理(newメソッドなど)
最後の理由で作る場合は仕方ありませんが、その他の理由については、クラス定義を作成する段階では予測しにくいことがほとんどです。そのため、クラスメソッドで作ったものの、インスタンスから利用したくなることも少なくありませんので、無理にクラスメソッドを作る必要はないでしょう。
まとめ
インスタンスと変数について、説明しました。
特にインスタンスの扱い方やインスタンス変数について、クラスとの関係など、少し深い内容になっていましたので、イメージしにくかったかもしれません。
また、Inst01-1.rbはいままでにない行数のプログラムでしたので、少々難解に見えたと思います。
しかし、これらの内容は、これからRubyプログラムの学習を進め、中級プログラマーになるためには、必ず通らなければならない部分ですので、何度も読んで理解を深めていただけることを祈ります。
概念的な説明も多いこういった部分は、やはり文字ではなく対面で人に教えてもらう方が明らかに理解が良いです。もし、機会があればスクールなどで質問してみると、効率が良いかもしれません。
・インスタンスは、クラスという設計図をもとに作られた「もの(オブジェクト)」
・クラスも「設計図」という「もの(オブジェクト)」
・インスタンス変数は、インスタンスの持っている値であり、メソッドはその値を操作するためのもの
・クラス変数はクラスが持っている値
・クラスインスタンス変数はクラスメソッドからのみアクセスできるクラスが持っている値
・インスタンスメソッドは、一般的なメソッドのことで、インスタンスからのみ利用できる
・インスタンス特有の特異メソッドを作ることで、インスタンスメソッドを追加できる
・クラス特有の特異メソッドは、クラスメソッドとなり、インスタンスがなくても使える