クラス
あらゆるものをオブジェクトとして扱うRubyでは、予め組み込まれているクラスのオブジェクトだけでなく自分でクラスを作れたりもする。
Rubyのオブジェクトとはそもそも
Rubyのオブジェクトは、クラス(ロボットの設計図)と
インスタンス(ロボット)でできている。
ロボットクラスを定義することで、それをもとにロボットを作成してつかうことができる。
クラスとインスタンスってなんやっけ
Rubyはオブジェクト指向の言語。
ここでのオブジェクトとは「データと手続きをまとめたモノ」。
ゲームで「ロボット」というオブジェクトを扱えば名前やヒットポイントなどの
データや移動、攻撃といった手続きを1つの単位にまとめることが出き
効率よくプログラムできる。
オブジェクトは、クラスとインスタンスの2つにわけて考える。
クラスはオブジェクトの設計図。その設計図に基づいて
実際にコンピュータのメモリ上に作られたオブジェクトがインスタンス。
クラスを記述するには「class クラス名 ~ end」という構文を使う。
例ロボットを表すRobotというクラスは下記のように記述する。
※クラス名は必ず大文字!!
class Robot
end
robo1 = Robot.new
p robo1
↑newメソッドでインスタンスを作成している。
newメソッドでRobotクラスの設計図に基づいたインスタンスが1つできて、
変数robo1はそのインスタンスを指し示している。
参照とコピーについて
代入演算子=は左辺の変数に「右辺の値への参照(オブジェクトリファレンス)」を
代入する、つまり左辺に右辺の値を参照させるという意味。
下記例では、s1とs2が同じインスタンスを指していることや、s1の変更がs2に反映されているのがわかる。
s1 = "hello"
s2 = s1
puts s1.object_id
puts s2.object_id
s1.upcase!
puts s2
複数の変数で別のオブジェクトを扱っていると認識していても上記例のように、
同じオブジェクトを複数の変数が指し示している場合がある。
それを防ぐためには、dupメソッドを使ってオブジェクトの複製を作ると良い。
下記例では、s1とs2は同じ文字列”hello”を持っているが、別のインスタンスを参照している。※dupメソッドはすべてのオブジェクトに用意されている。
s1 = "hello"
s2 = s1.dup
オブジェクトの種類を調べるには
ある変数が指しているオブジェクトが度のクラスのインスタンスであるかを調べるには
下記メソッドが役に立つ。
- classメソッド:クラスを返す
- kind_of?メソッドまたはis_a?メソッド:オブジェクトが引数に指定したクラスのインスタンスならtrueを返す。親クラスを指定してもtrueを返す。
- instance_of?メソッド:引数に指定したクラスのインスタンスならtrueを返す。親クラスを指定するとfalseになる。
s1 = "hello"
puts s1.class
puts s1.kind_of?(String) # Stringクラスだからtrue
puts s1.kind_of?(Object) # 親クラスObjectクラスだからtrue
puts s1.instance_of?(String) # Stringクラスだからtrue
puts s1.instance_of?(Object) # 親クラスObjectクラスだからfalse
インスタンスメソッドについて
インスタンスごとにデータを持たせるにはインスタンス変数を使う。
同じクラスのインスタンス間で共通の手続きは、インスタンスメソッドを定義する。
初期化とメソッド
クラスの中に「def メソッド名 ~ end」を記述するとインスタンスメソッドができる。
このインスタンスメソッドは「変数.メソッド名」のように呼び出してオブジェクトから情報を得たり、オブジェクトを操作したりできる。
class Robot
def move(x, y)
@x += x; @y += y
end
def to_s
"#{@name}: #{@x},#{@y}"
end
end
上記例では、現在位置を移動するmoveメソッドと、ロボットの情報を文字列で返すto_sメソッドをRobotクラスに加えるものです。
@をつけた変数はクラスのインスタンスごとに作られるインスタンス変数です。
インスタンス変数@nameと@x、@yに予め名前ト一を入れておくためには初期化用のメソッドが必要。
クラスの中には、initializeという初期化用のメソッドを置くことができる。initializeはnewでインスタンスを創る時に自動的に実行される。
initializeメソッドの引数の数は幾つでもいいですし、なくても大丈夫。下記例で引数nameの値をインスタンス変数@nameに保存させる
また、@x、@yの値を0にする。
class Robot
def initialize(name)
@name = name
@x = @y = 0
end
end
これで、例えばRobot.new(”ロボ1号”)のように引数付きでnewメソッドを呼び出すと、initializeの引数nameが「ロボ1号」になります。
initialize、move、to_sをまとめると下記プログラムができる。
※2つのRobotオブジェクトを作成し、それぞれの情報を表示するものとする。
class Robot
def initialize(name)
@name = name
@x = @y = 0
end
def move(x, y)
@x += x; @y += y
end
def to_s
"#{@name}: #{@x},#{@y}"
end
end
robo1 = Robot.new("ロボ1号") # ロボットのインスタンス1
robo2 = Robot.new("ロボ2号") # ロボットのインスタンス2
puts robo1
robo2.move(10, 20)
puts robo2
ロボ1号の現在位置は初期値の0,0ですが、2号ではメソッドで位置を変えているので、10,20になる。
上記例のようにインスタンス変数を使えば同じクラスのオブジェクトに別々のデータを持たせることができる。
インスタンス変数の初期値はnil
ローカル変数aをまだ作成していないときは、
「b = a」というコードを実行するとエラーになる。
インスタンス変数@aでは、いきなり「b = @a」としてもエラにはならず
bにはnilが入る。作成していないインスタンス変数はnilになる。
メソッド 呼び出し制限
Rubyではメソッド定義の上に、public、protected、privateを付けることで
呼び出しを制限できる。
下記にまとめる。
レベル | 機能 |
---|---|
public | メソッドはどこからでも呼び出せる |
protected | 同じクラスやサブクラス内のメソッドの中だけで呼び出せる。レシーバを付けても呼び出せる。 |
private | 同じクラスやサブクラス内のメソッドの中だけで呼び出せる。レシーバを付けたら呼び出せない。 |
privateメソッドを使った例は下記。
ロボットの位置が負の数になるとcrashメソッドを呼び出す。
crashメソッドはプライベートメソッドなので、
Robotクラス及びサブクラスの中でしか呼び出せない。
インスタンスメソッドmoveの中では呼べるがrobo1.crashのようにクラスの外では呼べない。
class Robot
def initialize(name)
@name = name
@x = @y = 0
end
def move(x, y) # パブリックメソッド
@x += x; @y += y
crash if @x < 0 || @y < 0
end
private # プライベートメソッド
def crash
puts "ドカン!!"
end
end
robo1 = Robot.new("ロボ1号") # ロボットのインスタンス1
robo1.move(200, -100) #エラー発生しない
robo1.crash # エラー発生する
レシーバとselfについて
robo1.moveのrobo1のようにメソッドを呼び出す対象をレシーバ(受け取るモノ)と呼ぶ。
Rubyではメソッドの呼び出しを
「オブジェクトに対してメッセージを送信する」と考えるから。
レシーバを省略すると現在のオブジェクトを表すselfがレシーバとみなされます。
属性の書き方について
インスタンス変数@nameを作っても、
オブジェクトの外からはrobo1.nameのようにインスタンス変数にアクセス出来ない。
robo1.nameのような書き方をしたいときは、変数と同名のメソッドを用意する必要がある。
下記例でのnameメソッド(読み出しメソッド)は戻り値として変数@nameを返すので、「name = robo1.name」で@nameを取り出せます。name=のようにメソッド名に=をつけると代入演算子の代わりとなるメソッド(書き込みメソッド)になる。
「robo1.name = “ロボ1号”」とするとname=メソッドが呼ばれ、代入演算子の右辺が引数になり、@nameが”ロボ1号”を指すようになる。
class Robot
def name # 名前の読み込み
@name
end
def name=(name) # 名前の書き込み
@name = name
end
end
robo1 = Robot.new
robo1.name = "ロボ1号"
puts robo1.name
上記例の用にオブジェクト内のデータにアクセスするにはメソッドを書かないといけない。
オブジェクト内のデータにアクセスするための読み出しや書き込み用のメソッドをアクセサメソッドと呼ぶ。
アクセサメソッドでやりとりできるデータを属性と呼ぶ。
※ここ大事
属性の実態は変数ではなく、インスタンス変数とメソッドの組み合わせです。
アクセサメソッドは簡単な書き方が用意されている。
class Robot
attr_reader :name # 読み出し用メソッドが追加される
attr_writer :name # 書き込み用メソッドが追加される
end
ちなみに読み書き両方のアクセサメソッドをつくるときは、attr_accessorをつかう。
メソッドが複数必要な場合、「attr_accessor :x, :y」と並べることができる。
次の例は、Robotクラスに読み出し専用の属性のnameと、
読み書きできる属性のscoreを設定したもの。
class Robot
attr_reader :name
attr_accessor :score
def initialize(name)
@name = name
@x = @y = 0
@score = 10
end
end
robo1 = Robot.new("ロボ1号")
robo2 = Robot.new("ロボ2号")
robo2.score = 90 # スコアを変更
puts robo1.name, robo1.score
puts robo2.name, robo2.score
クラス内での = 付きメソッドについて
アクセサメソッドはクラスの外からのほかにも、
クラス内の他のメソッドの中で呼ぶこともできる。
クラス内ではレシーバを省略してnameだけで呼べる。
ただし、=付きのメソッドでレシーバを省略するとローカル変数とみなされてしまう。
=付きのメソッドを呼ぶときは「self.name =」のようにレシーバselfを必ず付けること。
※どういうことかは下記例にて
def change_name(new_name)
old_name = old_name # nameメソッドの呼び出し
name = newz_name # 注意!nameはローカル変数になる
self.name = new_name # 正しいnameメソッドの呼び出し
end
本記事は下記本の勉強記事です。初学者にとても良い本です。