Ruby TIPS

Ruby TIPS

クラスとそのコンストラクター/アクセサー/メソッドを定義し利用するには?

2016年9月15日

Rubyプログラミングの基本中の基本として、クラスの定義から、そのインスタンスの作成・利用、インスタンスメソッドの定義、変数へのアクセスまでを説明する。

ローグ・インターナショナル 羽山 博
  • このエントリーをはてなブックマークに追加

 クラスを利用すれば、目的に合わせて変数手続き(=メソッド)をひとまとめにできる。一般に、クラスを定義し、それを基にクラスの実体(=インスタンス)を作って利用するというのが、クラスの初歩的かつ基本的な利用方法である。今回は、Rubyにおけるクラスの定義、インスタンスメソッドの定義、変数へのアクセス、インスタンスの作成と利用など、基本の基本を押さえることとしよう。

クラスの定義と利用

 クラスとは、大ざっぱにいえば変数とメソッドをひとまとめにしたものである。例えば、人間を表現するには、「身長」や「体重」といった変数と、「食べる」「寝る」といったメソッドをひとまとめにしておくと便利である。そのようにすれば、食べると体重が増える、といった処理を、別々に定義された変数とメソッドを使って表すよりも、簡潔に書ける。

 とはいえ、あまり抽象的な話をしても実感が湧かないので、簡単な例で見ていこう。経験のある人には自明な話なので、次の節まで読み飛ばしてもらっても構わない。

 さて、ここでは、平面上の点を表すクラスを例として取りあげよう。平面上の点は(デカルト座標系なら)、「X座標」と「Y座標」という変数で表される。メソッドとしては、「点の座標を設定するメソッド」や「点の座標を取得するメソッド」が考えられる。他にも、点を移動させるメソッドや原点の周りに回転させるメソッドなども考えられるが、今は、深入りはしないことにしよう。

 ここでは、クラスにPointという名前を付けることにする。以下の図の左側がPointクラスに含まれる変数とメソッドである。インスタンスの作成(=インスタンス化)については後述する。

クラス図
図1.1 クラスとインスタンス

PointクラスにはX座標とY座標を表す変数があり、座標の設定と座標の取得を行うメソッドがある。UML(Unified Modeling Language)のクラス図の書き方に沿っているので、項目の前に書かれている「-」はprivateを表し、「+」はpublicを表すのだが、取りあえずは気にしないでいてほしい。クラスの定義を基にインスタンスを作成すれば、実際の座標が表せる。

 では、図の左側に描かれているクラスの定義をRubyのコードで書いてみよう。クラスを定義するには、classの後にクラス名を書き、最後をendで閉じる。まずは大枠から書いてみる(リスト1.1)。

Ruby
class クラス名
  ……変数やメソッドの記述……
end
リスト1.1 クラスの定義方法

クラス定義の大まかな形式。変数やメソッドの記述はこの後で見ていく。

 クラス内の変数やメソッドはまとめてメンバーと呼ばれる。

 続いて、メンバーも書いてみよう。X座標を表す変数は@xとし、Y座標を表す変数は@yとする。詳細は後述するが、変数名の先頭が@になっていることに注目。

sample001.rb(前半)
class Point

  def set_point(x, y)
    @x = x
    @y = y
  end

  def get_point
    return @x, @y
  end

end
リスト1.1 Pointクラスを定義する

set_pointメソッドは変数@xと変数@yに値を設定するメソッド、get_pointメソッドは変数@xと変数@yの値を返すメソッドである。

 メソッド(=インスタンスメソッド)の書き方に特に難しい点はない。気になるのは@で始まる変数名であろう。このように、@で名前が始まる変数はインスタンス変数と呼ばれるもので、作成されたインスタンスの中でのみ使われる。インスタンスとは、いわばクラスの定義に基づいて作られた「実体」のようなものである。

 では、インスタンスを作成(=インスタンス化)してみよう。クラスからはインスタンスをいくつでも作成できる。プログラムの後半を以下に示す。書き方を確認しよう。

sample001.rb(後半)
p1 = Point.new
p1.set_point(10, 8)
p p1.get_point

p2 = Point.new
p2.set_point(7, 12)
p p2.get_point
リスト1.2 Pointクラスのインスタンスを作成する

newメソッドは新しいインスタンスを作成するためのメソッド。Pointクラスを基にインスタンスを作成し、変数p1で参照できるようにした。その後、set_pointメソッドで(10,8)という座標を設定した。さらにもう一つインスタンスを作成し、変数p2で参照できるようにした。その後、set_pointメソッドで(7,12)という座標を設定した。

 クラス名.newと書けば、クラスを基にインスタンスが作成できる。その時点ではまだ座標の値は設定されていないが、実際の「点」が作成されたことになる。その後、set_pointメソッドで座標の値を設定する。変数@xと変数@yはインスタンスごとに作成されるので、インスタンスp1とインスタンスp2とでは、異なる値が設定されている。念のため、前掲の図1.1を、リスト1.2を反映したようなものに描き変えて掲載しておこう(図1.2)。

クラス図
図1.2 Pointクラスから複数のインスタンスを作成する

Pointクラスの定義を基に2つのインスタンスを作成した。リスト1.2と比較して見てみると、インスタンスのp1p2が新たに描き加えられたことが分かる。

 実行例も確認しておこう。get_pointメソッドによって返された変数@xと変数@yの値を、pメソッドで出力しているだけである。

コンソール
$ ruby sample001.rb 
[10, 8]
[7, 12]
$ 
実行例1.1 クラスを利用したプログラムを実行する

インスタンスp1の変数@xの値は10、変数@yの値は8となり、インスタンスp2の変数@xの値は7、変数@yの値は12となる。

アクセサーの利用

 アクセサーを利用すれば、いちいちメソッドを定義しなくてもいいので、クラス定義の記述が簡潔になる。また、インスタンス変数に値を設定したり、値を取得したりするコードも簡単に書ける(リスト1.3)。

sample002.rb
class Point
  attr_accessor :x, :y
end

p1 = Point.new
p1.x = 4
p1.y = 5
p [p1.x, p1.y]
リスト1.3 アクセサーを利用する

attr_accessorの後に、変数のシンボルを記述する。この例であれば、:xがインスタンス変数@xに対応し、:yがインスタンス変数@yに対応する。「インスタンス名.シンボル」という書き方(先頭の:は除く)でインスタンス変数にアクセスできる。

 「:」で始まる名前はシンボルと呼ばれるもので、変数やメソッドを表すのに使われるオブジェクトである。変数の値を利用するためのものではなく、変数そのものを取り扱うために使われるもの、とでも考えるといいだろう。概念の理解が難しいようであれば、attr_accessorの後に書かれたシンボルが:xなら、@xという名前のインスタンス変数が作られ、xという名前でアクセスできるようになる、と覚えておけばいい。インスタンス変数に値を設定したり、インスタンス変数の値を利用したりする場合には、インスタンス名のp1に続けて.を書き、p1.xのようにすればよい。

 なお、attr_accessorの代わりにattr_readerと書けば読み出し専用のアクセサーになる。その場合、p1.x = 4p1.y = 5というコードはエラーになる。attr_writerと書けば、読み書きが可能なアクセサーになるので、attr_accessorと書いた場合と同じ結果になる(「writer」という単語は、意味的には「書き込み専用」だが、読み出しもできることに注意してほしい)。

 実行結果は以下の通り。

コンソール
$ ruby sample002.rb 
[4, 5]
$ 
実行例1.2 アクセサーを利用したプログラムを実行する

アクセサーを利用して、インスタンスp1の変数@x4を、変数@y5を代入し、そのまま結果を表示した

 変数@xや変数@yはインスタンスの中でのみ使われるので、当然のことながらp1.@xp1.@yとは書けない。もちろん、インスタンスの中のメソッドからであればインスタンス変数が使えるので、以下のように書けば、X座標の値とY座標の値をまとめて設定したり、まとめて返したりできる。

sample003.rb
class Point
  attr_accessor :x, :y

  def set_point(x, y)
    @x = x
    @y = y
  end

  def get_point
    return @x, @y
  end

end

p1 = Point.new
p1.x = 4
p1.y = 5
p p1.get_point
リスト1.4 アクセサーとメソッドの両方を利用する

アクセサーを利用して設定したインスタンス変数をget_pointメソッドの中で利用している。シンボルを表す:x、インスタンス変数を表す@x、アクセサーとして利用されるxのそれぞれの書き方の違いに注意しよう。実行結果は実行例1.2と同じ。

コンストラクターを書く

 コンストラクターとは、インスタンスが作成された時に自動的に実行されるメソッドのことである。Rubyではinitializeという名前のメソッドがコンストラクターになる。コンストラクターは、インスタンスの初期設定などに使われる。

 これも簡単な例で見てみよう。これまではインスタンスを作成してから、座標を設定していたが、以下のコードでは、インスタンスの作成時に座標の設定ができる。

sample004.rb
class Point
  def initialize(x, y)
    @x = x
    @y = y
  end

  def set_point(x, y)
    @x = x
    @y = y
  end

  def get_point
    return @x, @y
  end
end

p1 = Point.new(3, 8)
p p1.get_point
リスト1.5 コンストラクターを使って初期設定する

リスト1.1にinitializeメソッドを追加し、インスタンス変数@x@yに値を設定している。インスタンスを作成する時にinitializeメソッドが自動的に呼び出させる。initializeメソッドの仮引数であるxyに、newメソッドに指定した実引数の38が渡される。

 この例では、initializeメソッドとset_pointメソッドの働きは全く同じになっているが、initializeメソッドは暗黙的にprivateなメソッドになるので、例えば、p1.initialize(10, 12)のような呼び出し方はできない。一方、コンスタラクター以外のメソッドはpublicなメソッドになる。従って、set_pointメソッドについては、p1.set_point(10, 12)のような呼び出し方ができる。

 とはいえ、このように同じ内容のメソッドが2つあるコードは冗長なので、なんとなく落ち着かない。そこで、alias文を使ってinitializeメソッドにset_pointという別名を付けて、メソッドの定義を一つにまとめてみよう。alias文の書き方は、

  alias 新しいメソッド名 元のメソッド名

である。ただし、単純に別名を付けるだけだと、set_pointメソッドもinitializeメソッドと同じようにprivateなメソッドになってしまうので、public宣言もしておく。

sample005.rb
class Point

  def initialize(x, y)
    @x = x
    @y = y
  end

  alias set_point initialize   # メソッドに別名を付ける
  public :set_point  # set_pointメソッドをpublicにする

  def get_point
    return @x, @y
  end
end

p1 = Point.new(3, 8)
p1.set_point(12, 9)  # 座標を変更する
p p1.get_point
リスト1.6 メソッドに別名を付ける

alias文を使ってinitializeメソッドにset_pointという別名を付ける。さらに、set_pointメソッドをpublicにしておく。publicの後にはメソッドのシンボル名を指定することに注意。initializeメソッドはprivateのままである。

 実のところ、わざわざaliasを使って別名を付けたり、publicを指定したりしなくても、リスト1.5のinitializeメソッドからset_pointメソッドを呼び出すようにするだけでもいい。本来は、その方が簡単で分かりやすいのだが、こういうこともできるという書き方の例として示しておいた。念のため、initializeメソッドからset_pointメソッドを呼び出した場合のコードと実行例も示しておこう。

sample006.rb
class Point

  def initialize(x, y)
    set_point(x, y)
  end

  def set_point(x, y)
    @x = x
    @y = y
  end

  def get_point
    return @x, @y
  end
end

p1 = Point.new(3, 8)
p1.set_point(12, 9)  # 座標を変更する
p p1.get_point
リスト1.7 initializeメソッドからset_pointメソッドを呼び出す

メソッドから同じクラス内の別のメソッドも、もちろん呼び出せる。ここではinitializeメソッドに渡された引数をそのままset_pointメソッドに渡して呼び出している。

コンソール
$ ruby sample006.rb 
[12, 9]
$
実行例1.3 別名を付けたメソッドを呼び出したプログラムの実行例

newメソッドで設定した座標をset_pointメソッドで変更しているので、実行結果が[12, 9]となることは容易に分かる。

クラスメソッドを定義する

 ここまでは、クラスを基にインスタンスを作成し、インスタンスメソッドを定義し、それを呼び出して利用する例を見てきたが、(インスタンスではなく)クラスそのものの機能として、メソッドを定義することもできる。それがクラスメソッドである。

 クラスメソッドはインスタンスを作らなくても利用できる。例えば、原点(0,0) を返すメソッドは、それぞれのインスタンスによって異なる値を扱うわけではないので、クラスメソッドにした方が都合がいい。リスト1.6にクラスメソッドを追加してみよう。

sample007.rb
class Point

  def Point.origin  # クラスメソッドの定義
    return 0, 0
  end

  def initialize(x, y)
    set_point(x, y)
  end

  def set_point(x, y)
    @x = x
    @y = y
  end

  def get_point
    return @x, @y
  end
end

p Point.origin
リスト1.8 クラスメソッドを定義する

クラスメソッドを定義するには、メソッドを定義するときに、クラス名.メソッド名という名前を指定すればよい。ここでは、originというメソッド名にしてある。最後の行でクラスメソッドを呼び出しているが、インスタンスを作成していないことに注目。

 クラスメソッドはクラス名.メソッド名という名前で定義する。例えばPointクラスのoriginメソッドであれば、Point.originという名前で定義すればよい。クラスメソッドを呼び出すときには、インスタンスを作成するのではなく、単にクラス名.メソッド名と書けばよい。ここでは、引数のないメソッドを定義しているが、もちろん引数を指定することもできる。実行例は示すまでもないだろう([0, 0]となる)。

 なお、Rubyでは個々のオブジェクトに対してメソッドを定義できる。そのようなメソッドのことを特異メソッドと呼ぶ。実は、クラスメソッドも特異メソッドの一つである。蛇足ながら、特異メソッドの例を示しておこう。

sample008.rb
str = "Hello"

def str.longer(target)
  return (self.length > target.length)? self : target
end

p str.longer("Ruby") 
リスト1.9 特異メソッドを定義する

変数strStringオブジェクトを参照する。ここでは、str.longerという特異メソッドを定義している。このメソッドの内容は、自分自身と引数で指定した文字列の長い方を返すというもの。ただし、同じ長さの場合は引数に指定した文字列が返される。実行例は省略する("Hello"と表示されるだけなので)。

 このようにして、Rubyでは、オブジェクトにメソッドを追加することもできる。クラスもオブジェクトの一種なので、特異メソッドを定義すれば、クラスメソッドとして利用できるというわけである。

 今回はクラスの基本を一通り見たが、継承などの機能については触れる余裕がなかった。次回は継承も含め、演算子の定義やオーバーライドなど、高度な機能をいくつか取り上げることとしたい。

まとめ

 クラスを定義し、それを基にインスタンスを作成すれば、同じ形式のオブジェクトをいくつも作成できる。インスタンス変数はそれぞれのインスタンスで使われる変数であり、アクセサーを利用すれば簡単に値の設定や取得ができる。インスタンスの作成時にはinitializeという名前のメソッドが自動的に呼び出される。そのメソッドのことをコンストラクターと呼ぶ。クラスにはインスタンスを作成しなくても利用できるクラスメソッドを定義できる。

処理対象:インスタンスメソッド|インスタンス変数|アクセサー|コンストラクター|クラスメソッド|特異メソッド カテゴリ:文法 > クラス
処理対象:alias文 カテゴリ:文法 > クラス > クラス/メソッドの定義に関する操作

※以下では、本稿の前後を合わせて5回分(第10回~第14回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

10. for文 ― ちょっと便利な繰り返し処理の構文とは?(2)

Rubyに用意されている繰り返し処理の構文のうち、for文の基本的な使い方と配列と組み合わせた利用例を解説。範囲や配列を利用して一定の回数だけ繰り返して文を実行する方法や、配列全体を処理する方法を紹介する。

11. ブロック ― ちょっと便利な繰り返し処理の構文とは?(3)

Rubyに用意されている繰り返し処理の構文のうち「ブロック」を使えば、繰り返し処理をより簡潔に書ける。その基本的な使い方と、自作メソッドでの利用例を解説する。

12. 【現在、表示中】≫ クラスとそのコンストラクター/アクセサー/メソッドを定義し利用するには?

Rubyプログラミングの基本中の基本として、クラスの定義から、そのインスタンスの作成・利用、インスタンスメソッドの定義、変数へのアクセスまでを説明する。

13. クラスを継承するには? メソッドの呼び出しをprivate/protectedで制限するには?

オブジェクト指向言語の特長である「クラスの継承」をRubyで実現する方法を解説。スーパークラスのメソッドの呼び出し制限で、Ruby言語特有の内容についても紹介する。

14. クラスのメソッドをオーバーライドするには?

継承先クラスの新メソッドで元クラスの既存メソッドをオーバーライドして異なる機能に置き換える方法と、新メソッド内から既存メソッドを呼び出すことで既存機能に新機能を追加する方法を説明する。

サイトからのお知らせ

Twitterでつぶやこう!