オブジェクト指向プログラミング


プログラミングにはパラダイムという、プログラム開発へのアプローチが複数存在する。

プログラミングパラダイムの例:

プログラミング言語によって、対応できるパラダイムが異なる。例えば、C言語は手続き型、そしてHaskellは関数型のプログラミング言語として設計された。JavaやRubyなどではオブジェクト指向プログラミングがメインである。

オブジェクト指向プログラミング(object-oriented programming、略:OOP)ではコンピュータプログラムは、お互いにやりとりをするオブジェクトから構成されている。

Ruby言語には数値や文字列や真偽値など、プログラムの中で扱う値はすべてオブジェクトであるという特徴がある。


オブジェクトとクラス

オブジェクト指向プログラミングにおけるオブジェクトとは、あるものをプログラムの中で表現するために必要なデータ(変数)と処理(メソッド)をまとめたものである。オブジェクトはメソッドを通じてデータを処理したり、他のオブジェクトとやりとりしたりする。あるオブジェクトが持つメソッドを呼び出すには下記のような構文を使う。

オブジェクト.メソッド名(引数)

例:

text = "This is a text"
txt_len = text.length  # => 14
[1,2].push(3)  # => [1, 2, 3]

解説:この例では、text という変数に格納したテキストは文字列型のオブジェクトで、[1,2] は配列型のオブジェクトである。

実世界に存在するものと同様に、プログラミングで扱うオブジェクトには様々な種類がある。これらの種類のことをクラス(class)という。どのクラスに属するかによってオブジェクトは異なる特徴(データとメソッド)を持っている。

数値や文字列や配列など、これまで習ってきたRubyの「データ型」もすべてクラスである。例えば、文字列は String クラスのオブジェクトで、配列は Array クラスのオブジェクトである。数値は Numeric クラスに属している。Numeric クラスはさらに整数の Integer クラスと浮動小数点数の Float クラスに分けられる。

オブジェクトのクラスを調べるには class メソッドを使う。例えば、

 練習問題 irbで実行すること)
num = 10
num.class # => Integer
1.05.class  # => Float
"This is a text".class # => String

あるクラスに属するオブジェクトのことを、そのクラスのインスタンス(instance、「実例」)と呼ぶことが多い。


クラスの定義

クラスは自分で定義することもできる。クラスの定義はオブジェクト作成の雛型のようなもので、そのクラスに属するオブジェクトのすべてに共通する情報とメソッドを記述している。クラスを定義するには以下の構文を使う。

class クラス名
  メソッドの定義など
end

注意点:クラス名の先頭文字はアルファベットの大文字でなければならない。

定義したクラスのインスタンスを作るには new メソッドを使う。

クラス名.new

例:

# 「人」クラスの定義
class Person
end

# (2つの)インスタンスの作成
person1 = Person.new
person2 = Person.new

コンストラクタ

コンストラクタとは、クラスのインスタンスを作る際に自動的に実行され、主にオブジェクトが持つ変数の初期化に用いられる特別なメソッドである。Rubyでは initialize メソッドがコンストラクタとして機能している。クラスの定義で initialize メソッドを定義し、その定義で引数を指定すると new メソッドで渡した引数の値が initialize メソッドに渡される。

例:

 練習問題  person.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
  
class Person

  def initialize(name)
    puts "Personクラスのインスタンスを生成します"
    @name = name # インスタンス変数
  end
  
end

tarou = Person.new("Tarou")

インスタンス変数

上記の例で頭文字が「@」の変数はインスタンス変数といい、インスタンス内部で値を保持するために使われる。インスタンス変数はインスタンス内部で使用される変数で、同じインスタンスのメソッドであれば共通して使用できる。

例:

 練習問題  person2.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
    
class Person

  def initialize(name)
    msg = "Personクラスのインスタンスを生成します" # ローカル変数(このメソッド中でしか参照できない)
    puts msg
    @name = name # インスタンス変数の初期化
  end

  def say_hello()
    puts "Hello, I'm " + @name
  end

  def change_name(new_name)
    puts @name + " の名前を " + new_name + " に変更します"
    @name = new_name
  end
  
end

person1 = Person.new("Tarou")
person2 = Person.new("John")
person1.say_hello # => "Hello, I'm Tarou"
person2.say_hello # => "Hello, I'm John"
person1.change_name("Takeshi") # => "Tarou の名前を Takeshi に変更します"

クラス変数

インスタンス変数の他に、変数名が「@@」から始まるクラス変数もある。クラス変数は特定のオブジェクトに属するのではなくクラス全体で共有され、そのクラスのすべてのインスタンスから値を参照したり変更したりすることができる。

例:

 練習問題  circle.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

class Circle
  
  @@PI = 3.141592

  def initialize(r)
    @radius = r # 半径
  end

  def area() # 面積
    @radius**2 * @@PI
  end

  def circumference() # 円周
    2 * @@PI * @radius
  end
  
end

circle = Circle.new(10)
puts circle.area # => 314.1592
puts circle.circumference # => 62.83184

 練習問題  car.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

class Car
  
  @@n_wheels = 4 # 車輪の数(変わらないのでクラス変数)
  @@count = 0 # これまで生産された車の数

  def initialize(type, color, seats)
    @@count += 1
    printf("%d台目の自動車を生産します\n", @@count)
    @type = type
    @color = color
    @n_seats = seats
  end
  
end

car1 = Car.new("軽自動車", "白", 4) # => 1台目の自動車を生産します
car2 = Car.new("普通車", "黒", 5) # => 2台目の自動車を生産します
car3 = Car.new("スポーツカー", "赤", 2) # => 3台目の自動車を生産します

ゲッターとセッター

インスタンス変数の参照および更新はゲッターとセッターという特別なメソッドを通して行う必要がある。下記のようにオブジェクトの属性を外部から直接アクセスしようとするとエラーになる。

 練習問題  person3.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

class Person

  def initialize(name)
    @name = name
  end

end

person = Person.new("Tarou")
puts person.@name # => エラーになる

インスタンス変数の更新についても同じである。

 練習問題  person4.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
  
class Person

  def initialize(name)
    @name = name
  end

end

person = Person.new("Tarou")
person.@name = "John" # => エラーになる

このようにオブジェクトの内部状態(属性)が隠されていることをカプセル化という。

ゲッターとセッターを使うと以下の通りになる。

 練習問題  person5.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

class Person

  def initialize(name)
    @name = name
  end

  # (@nameを参照するための)ゲッター
  def name
    @name
  end

  # (@nameを更新するための)セッター
  def name=(name)
    @name = name
  end

end

person = Person.new("Tarou")
puts person.name # => Tarou
person.name = "John" 
puts person.name # => John

属性が取り得る値について何らかの制限があればセッターを使ってそれを管理できる。例えば、下記のプログラムでは @name のセッターで、/[^a-zA-Z]/ という正規表現を用いて与えられたが文字列がローマ字以外の文字を含むかの確認を行って、もしそうであればインスタンス変数を更新しない。

 練習問題  person6.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
  
class Person

  def initialize(name)
    @name = name
  end

  # ゲッター
  def name
    @name
  end

  # セッター
  def name=(name)
    if /[^a-zA-Z]/ =~ name
      puts "名前はローマ字だけで入力してください"
    else
      @name = name
      puts "名前が更新されました"
    end
  end

end

person = Person.new("John")
person.name = "太郎" # => 名前はローマ字だけで入力してください
person.name = "Tarou" # => 名前が更新されました

クラスの継承

既に存在するクラスを基に、そのクラスが持っているメソッドを修正したり、新しいメソッドを追加したりすることで新しいクラスを作ることができる。これをクラスの継承という。クラスを継承するにはクラス定義の1行目を以下のように記述する。

class 新しいクラス < 既存のクラス

そうすることによって同じソースコードを繰り返さずに、既存のクラスの機能をすべて受け継ぐ拡張クラスを定義できる。また、複数のクラスに共通の部分がある場合、一か所にまとめることができる。例えば、Integer と Float はどちらも数値であり一部の処理メソッドが同じであるため、両方とも Numeric クラスを継承している構造になっている。

クラス継承の例:

 練習問題  animals.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
  
class Mammal # 哺乳類
  
  def make_sound()
    puts "哺乳類が音を出した"
  end

end

class Primate < Mammal # 霊長類
  
  def walk()
    puts "霊長類が歩いた"
  end

end

class Human < Primate # 人間
  
  def speak()
    puts "人間が話した"
  end

end

human = Human.new
human.make_sound # => 哺乳類が音を出した
human.walk # => 霊長類が歩いた
human.speak # => 人間が話した

解説:人間は霊長類でもある(Primateクラスを継承している)し、霊長類は哺乳類でもある(Mammalクラスを継承している)ため、HumanクラスのインスタンスはPrimateクラスとMammalクラスに備わっている機能(make_soundメソッドとwalkメソッド)も持っている。

親クラスのメソッド(コンストラクタを含む)を拡張したいときは、メソッド定義に super キーワードを入れると、親クラスのメソッドの中から super が入っているメソッドと同じ名前のメソッドを呼び出す。例えば、

 練習問題  animals2.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
  
class Primate # 霊長類
  
  def initialize(height, age)
    @height = height
    @age = age
  end

  def walk()
    print "霊長類が歩いた"
  end

end

class Human < Primate # 人間
  
  def initialize(height, age, name)
    super(height, age) # Primate の コンストラクタを呼び出す
    @name = name
  end

  def walk()
    super # Primate の walk メソッドを呼び出す
    puts "(人間だから二足歩行)"
  end

end

human = Human.new(180, 30, "John")
human.walk # => 霊長類が歩いた(人間だから二足歩行)

解説:人間は他の霊長類と異なって名前という属性を持っているため、super を使ってコンストラクタを拡張している。また、二足歩行をするという特徴があるため、walk メソッドも拡張されている。


本日の課題

 基本課題 

長方形を表すクラス Rectangle を定義せよ。

クラスの属性(インスタンス変数):

  1. width (幅)
  2. length (長さ)

クラスのメソッド:

  1. コンストラクタ (引数:幅、長さ)
  2. width 用のゲッターとセッター
  3. length 用のゲッターとセッター
  4. calcArea (面積を計算するメソッド)
  5. calcPerimeter (周囲の長さを計算するメソッド)

定義したクラスのインスタンスを3つ程度作って、それぞれのメソッドの動作を確認すること。

 発展課題 

  1. Rectangleクラスに、長方形の対角線を計算するメソッド calcDiagonal を追加せよ。
  2. Rectangleクラスを、長方形の長さと幅を1~100の範囲でのみ設定できるように改良せよ。
  3. Rectangleクラスを継承し、直方体を表すクラス Cuboid を定義せよ。その中で、表面積および体積を計算するメソッドを定義すること。

目次