1. ホーム
  2. スクリプト・コラム
  3. ルビートピックス

Rubyオブジェクト指向の知識まとめ

2022-02-07 20:52:46

Rubyは純粋にオブジェクト指向の言語です。Rubyに含まれるすべてのものはオブジェクトであり、Rubyのすべての値はオブジェクトです。最も原始的なものでさえ、文字列、数値、trueとfalseさえオブジェクトなのです。クラスはそれ自体がオブジェクトであり、Class クラスのインスタンスである。この章では、Rubyのオブジェクト指向に関連するすべての主要な機能を説明します。

クラスは、データ表現とメソッドを組み合わせたオブジェクトの形を指定し、データをきちんとパッケージ化して整理するために使われます。クラス内のデータやメソッドは、クラスのメンバと呼ばれます。

Rubyのクラス定義

クラスを定義するとき、実際にはデータ型の青写真を定義していることになります。これは実際にはデータを定義するのではなく、クラスの名前が意味するもの、つまり、そのクラスのオブジェクトが何から構成され、そのオブジェクトに対してどのような操作が可能かを定義しているのです。

クラス定義は、キーワードclassで始まり、クラス名が続き、endで終わります。例えば、キーワードclassを使ってBoxクラスを定義すると、次のようになります。

class Box
  code
end

慣習上、名前は大文字で始まり、複数の単語を含む場合はそれぞれの単語が大文字になりますが、単語の間に区切りはありません(例:CamelCase)。

Rubyオブジェクトの定義

クラスはオブジェクトの設計図を提供するので、基本的にオブジェクトはクラスを元に作成されます。クラスのオブジェクトを宣言するにはnewキーワードを使用します。次の文は、クラスBoxの2つのオブジェクトを宣言しています。

box1 = Box.new
box2 = Box.new

初期化メソッド

initialize メソッドは Ruby の標準的なクラスメソッドで、他のオブジェクト指向プログラミング言語におけるコンストラクタの動作に似ています。initialize メソッドは、オブジェクトを作成する際にクラス変数を初期化したい場合に便利です。このメソッドは一連の引数を取り、他の Ruby メソッドと同様に、以下のように def キーワードを前に置く必要があります。

class Box
  def initialize(w,h)
   @width, @height = w, h
  end
end

インスタンス変数

インスタンス変数はクラスの属性で、そのクラスを使ってオブジェクトを作成すると、そのオブジェクトのプロパティになります。各オブジェクトのプロパティは個別に割り当てられ、他のオブジェクトと値を共有することはありません。クラス内部では、これらのプロパティに@演算子でアクセスし、クラス外部では、アクセッサメソッドと呼ばれるパブリックメソッドでアクセスします。上で定義したクラスBoxを例に、@widthと@heightをクラスBoxのインスタンス変数として使ってみましょう。

class Box
  def initialize(w,h)
   # Assign values to the instance variables
   @width, @height = w, h
  end
end

アクセサ(ゲッター) & セッターメソッド

クラス内で定義された変数をクラス外から読み出すには、アクセッサ(ゲッター)メソッドを定義することでアクセスできます。次の例は、アクセッサメソッドの使い方を示しています。 <未定義

#! /usr/bin/ruby -w
 
# Define the class
class Box
  # Constructor
  def initialize(w,h)
   @width, @height = w, h
  end
 
  # Accessor methods
  def printWidth
   @width
  end
 
  def printHeight
   @height
  end
end
 
# Create an object to initialize the height and width of the box
box = Box.new(10, 20)
 
# Use the accessor method
x = box.printWidth()
y = box.printHeight()
 
puts "boxWidth : #{x}"
puts "boxHeight : #{y}"

上記のコードを実行すると、以下のような結果が得られます。

ボックスの幅:10
ボックスの高さ:20

変数の値にアクセスするためのアクセサメソッドと同様に、Rubyには、クラス内ですでに定義されている変数にクラス外の引数を渡す方法があり、次のように定義されるセッターメソッドと呼ばれます。

#! /usr/bin/ruby -w
 
# Define the class
class Box
  # Constructor methods
  def initialize(w,h)
   @width, @height = w, h
  end
 
  # Accessor methods
  def getWidth
   @width
  end
  def getHeight
   @height
  end
 
  # Setter methods
  def setWidth=(value)
   @width = value
  end
  def setHeight=(value)
   @height = value
  end
end
 
# Create the object
box = Box.new(10, 20)
 
# Use the setter method
box.setWidth = 30
box.setHeight = 50
 
# Use the accessor method
x = box.getWidth()
y = box.getHeight()
 
puts "boxWidth : #{x}"
puts "boxHeight : #{y}"

上記のコードを実行すると、以下のような結果が得られます。

ボックスの幅:30
ボックスの高さ:50

この2つのメソッドは非常によく使われるので、Rubyでは3つの属性宣言メソッド attr_accessor :変数名, attr_reader :変数名, attr_writer :変数名を定義しています。ここで: accessor=reader+writer となります。

また、変数名の前には : が必要で、変数名は , で区切られることに注意してください。

インスタンスメソッド

インスタンスメソッドは、他のメソッドと同様にdefキーワードで定義しますが、以下の例に示すように、クラスインスタンスを通してのみ使用することができます。その機能はインスタンス変数へのアクセスにとどまらず、必要な限りのタスクを実行することができます。

#! /usr/bin/ruby -w
 
# Define the class
class Box
  # Construct method
  def initialize(w,h)
   @width, @height = w, h
  end
  # Instance methods
  def getArea
   @width * @height
  end
end
 
# Create the object
box = Box.new(10, 20)
 
# Call the instance method
a = box.getArea()
puts "Area of the box is : #{a}"

上記のコードを実行すると、以下のような結果が得られます。

ボックスの面積:200

クラスメソッドとクラス変数

クラス変数とは、クラスの全インスタンスで共有される変数です。言い換えれば、クラス変数のインスタンスは、オブジェクトのすべてのインスタンスからアクセスすることができます。クラス変数の先頭には2つの @ 文字 (@@) が付き、次の例に示すように、クラス変数はクラス定義の中で初期化する必要があります。

クラスメソッドはdef self.methodname()で定義し、クラスメソッドはend separatorで終了させる。クラスメソッドは、次の例のようにクラス名と一緒にclassname.methodname形式で呼び出すことができます。

#! /usr/bin/ruby -w
 
class Box
  # Initialize class variables
  @@count = 0
  def initialize(w,h)
   # Assign values to instance variables
   @width, @height = w, h
 
   @@count += 1
  end
 
  def self.printCount()
   puts "Box count is : #@@count"
  end
end
 
# Create two objects
box1 = Box.new(10, 20)
box2 = Box.new(30, 100)
 
# Call the class method to output the box count
Box.printCount()

上記のコードを実行すると、以下のような結果が得られます。

ボックス数:2

to_sメソッド

定義したどのクラスも、オブジェクトの文字列表現を返すto_sインスタンスメソッドを持っています。ここでは、Boxオブジェクトを幅と高さに基づいて表現する簡単な例を示します。

#! /usr/bin/ruby -w
 
class Box
  # Constructor methods
  def initialize(w,h)
   @width, @height = w, h
  end
  # Define the to_s method
  def to_s
   "(w:#@width,h:#@height)" # String format of the object
  end
end
 
# Create the object
box = Box.new(10, 20)
 
# Automatically call the to_s method
puts "String representation of box is : #{box}"

上記のコードを実行すると、以下のような結果が得られます。

ボックスの文字列表現は: (w:10,h:20)です。

アクセス制御

Rubyでは、インスタンスメソッドの保護はpublic, private, protectedの3段階があり、インスタンス変数やクラス変数にはアクセス制御はかかりません。

  • パブリック・メソッド。パブリックメソッドは、どのようなオブジェクトからも呼び出すことができます。デフォルトでは、メソッドはすべてパブリックですが、initialize メソッドは常にプライベートです。
  • プライベートメソッド。プライベートメソッドは、クラスの外からアクセスしたり、見ることができません。クラスメソッドのみがプライベートメンバーにアクセスすることができます。
  • プロテクトされたメソッド。保護されたメソッドは、クラスとそのサブクラスのオブジェクトによってのみ呼び出すことができます。また、アクセスはクラスとそのサブクラス内でのみ可能です。

以下は、これら3つの修飾子の構文を示す簡単な例です。

#! /usr/bin/ruby -w
 
# Define the class
class Box
  # Constructor methods
  def initialize(w,h)
   @width, @height = w, h
  end
 
  # Instance methods are public by default
  def getArea
   getWidth() * getHeight
  end
 
  # Define the private accessor methods
  def getWidth
   @width
  end
  def getHeight
   @height
  end
  # make them private
  private :getWidth, :getHeight
 
  # Example methods for outputting area
  def printArea
   @area = getWidth() * getHeight
   puts "Big box area is : #@area"
  end
  # Let the instance method be protected
  protected :printArea
end
 
# Create the object
box = Box.new(10, 20)
 
# Call the instance method
a = box.getArea()
puts "Area of the box is : #{a}"
 
# Try to call the protected instance method
box.printArea()

上記のコードを実行すると、以下のような結果になります。ここでは、1つ目のメソッドは正常に呼び出されますが、2つ目のメソッドで問題が発生します。

ボックスの面積:200
test.rb:42: protected method `printArea' called for #.
<Box:0xb7f11280 @height=20, @width=10> (NoMethodError)です。

クラスの継承

オブジェクト指向プログラミングにおいて、継承は最も重要な概念の一つである。継承によって、他のクラスをベースにしてクラスを定義できるようになり、アプリケーションの作成と保守が容易になります。

Rubyは多重継承をサポートしていませんが、ミキシンをサポートしています。ミキシンは多重継承の特定の実装のようなもので、インタフェース部分のみを継承することができます。

クラスを作成する際、プログラマーは新しいクラスが既存のクラスのメンバーを継承することを直接指定できるので、新しいデータメンバーやメンバー関数をゼロから書く必要はありません。この既存のクラスを基底クラスまたは親クラスと呼び、新しいクラスを派生クラスまたは子クラスと呼びます。

Ruby にはサブクラス化、すなわち継承の概念もあり、以下の例で説明します。クラスを拡張するための構文は非常に簡単です。class文に<文字と親クラスの名前を加えるだけです。たとえば、BoxのサブクラスとしてBigBoxというクラスを定義するのは次のとおりです。

#! /usr/bin/ruby -w
 
# Define the class
class Box
  # Constructor methods
  def initialize(w,h)
   @width, @height = w, h
  end
  # Instance methods
  def getArea
   @width * @height
  end
end
 
# Define subclasses
class BigBox < Box
 
  # Add a new instance method
  def printArea
   @area = @width * @height
   puts "Big box area is : #@area"
  end
end
 
# Create the object
box = BigBox.new(10, 20)
 
# Output area
box.printArea()

上記のコードを実行すると、以下のような結果が得られます。

ビッグボックスの面積は:200

メソッドのオーバーロード

派生クラスには新しい機能を追加できますが、時には親クラスで既に定義されているメソッドの動作を変更したい場合があります。この場合、次の例に示すように、メソッド名を変更せずに、単純にメソッドの機能をオーバーロードすることができます。

#! /usr/bin/ruby -w
 
# Define the class
class Box
  # Constructor methods
  def initialize(w,h)
   @width, @height = w, h
  end
  # Instance methods
  def getArea
   @width * @height
  end
end
 
# Define subclasses
class BigBox < Box
 
  # Change the existing getArea method
  def getArea
   @area = @width * @height
   puts "Big box area is : #@area"
  end
end
 
# Create object
box = BigBox.new(10, 20)
 
# Output area using the overloaded method
box.getArea()

上記の実行例の出力は

ビッグボックスの面積は:200

演算子のオーバーロード

2 つの Box オブジェクトのベクトル加算を行う + 演算子、Box の幅と高さを乗算する * 演算子、Box の幅と高さを反転する単項演算子 - を使用したいと思います。以下は、Box クラスに数学演算子を定義したバージョンです。

class Box
 def initialize(w,h) # Initialize width and height
  @width,@height = w, h
 end
 
 def +(other) # Define + to perform vector addition
  Box.new(@width + other.width, @height + other.height)
 end
 
 def -@ # Define the unary operator - to invert width and height
  Box.new(-@width, -@height)
 end
 
 def *(scalar) # Perform scalar multiplication
  Box.new(@width*scalar, @height*scalar)
 end
end

オブジェクトの凍結

時には、オブジェクトが変更されるのを防ぎたいことがあります。Objectでは、freezeメソッドがこれを実現し、オブジェクトを効果的に定数に変えます。Object.freezeを呼び出すことで、どんなオブジェクトでもフリーズさせることができます。フリーズしたオブジェクトは変更することができません。つまり、そのインスタンス変数を変更することはできません。

Object.frozen? メソッドを使用すると、指定したオブジェクトがフリーズしているかどうかを確認できます。このメソッドは、オブジェクトがフリーズしている場合は true を返し、そうでない場合は false を返します。次の例でこの概念を説明します。

#! /usr/bin/ruby -w
 
# Define the class
class Box
  # Constructor methods
  def initialize(w,h)
   @width, @height = w, h
  end
 
  # Accessor methods
  def getWidth
   @width
  end
  def getHeight
   @height
  end
 
  # Setter methods
  def setWidth=(value)
   @width = value
  end
  def setHeight=(value)
   @height = value
  end
end
 
# Create the object
box = Box.new(10, 20)
 
# Let's freeze the object
box.freeze
if( box.frozen? )
  puts "Box object is frozen object"
else
  puts "Box object is normal object"
end
 
# Now try using the setter method
box.setWidth = 30
box.setHeight = 50
 
# Use the accessor method
x = box.getWidth()
y = box.getHeight()
 
puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"

上記のコードを実行すると、以下のような結果が得られます。

Boxオブジェクトはフリーズしたオブジェクト
test.rb:20:in `setWidth=': フリーズしたオブジェクトを変更できない (TypeError)
        test.rb:39より

クラス定数

クラス内部で定数を定義するには、変数に直接数値または文字列を代入します。定数は @ や @@ を使用せずに定義します。慣習として、定数名は大文字で表記されます。

定数は一度定義すると、その値を変更することはできません。定数には、変数と同じようにクラスの内部から直接アクセスできますが、クラスの外部から定数にアクセスする場合は、次の例のようにclassname::constantを使用しなければなりません。

#! /usr/bin/ruby -w
 
# Define the class
class Box
  BOX_COMPANY = "TATA Inc"
  BOXWEIGHT = 10
  # Constructor methods
  def initialize(w,h)
   @width, @height = w, h
  end
  # Instance methods
  def getArea
   @width * @height
  end
end
 
# Create the object
box = Box.new(10, 20)
 
# Call the instance method
a = box.getArea()
puts "Area of the box is : #{a}"
puts Box::BOX_COMPANY
puts "Box weight is: #{Box::BOXWEIGHT}"

上記のコードを実行すると、以下のような結果が得られます。

ボックスの面積:200
株式会社タタ
箱の重量は 10

クラス定数はインスタンスメソッドと同様に継承、オーバーロードが可能です。

allocateを使ったオブジェクトの作成

オブジェクトのコンストラクタinitializeを呼ばずに、つまりnewメソッドを使ってオブジェクトを作成したい場合があります。その場合は、次の例のようにallocateを呼んで、初期化されていないオブジェクトを作成することが可能です。

#! /usr/bin/ruby -w
 
# Define the class
class Box
  attr_accessor :width, :height
 
  # Constructor methods
  def initialize(w,h)
   @width, @height = w, h
  end
 
  # Instance methods
  def getArea
   @width * @height
  end
end
 
# Create an object using new
box1 = Box.new(10, 20)
 
# Use allocate to create another object
box2 = Box.allocate
 
# Use box1 to call the instance method
a = box1.getArea()
puts "Area of the box is : #{a}"
 
# Use box2 to call the instance method
a = box2.getArea()
puts "Area of the box is : #{a}"

上記のコードを実行すると、以下のような結果が得られます。

ボックスの面積:200
test.rb:14: 警告: インスタンス変数 @width が初期化されていません。
test.rb:14: 警告: インスタンス変数 @height が初期化されていません。
test.rb:14:in `getArea': 未定義のメソッド `*' です。 
   for nil:NilClass (NoMethodError) from test.rb:29

クラス情報

Rubyのselfは、Javaのthisと似ていますが、大きく異なります。Javaのメソッドはインスタンスメソッドで参照されるので、これは一般に現在のオブジェクトを指します。Rubyのコードは一行ずつ実行されるので、selfは文脈によって異なる意味を持つ。次の例を見てみましょう: .

#! /usr/bin/ruby -w
 
class Box
  # Output class information
  puts "Class of self = #{self.class}"
  puts "Name of self = #{self.name}"
end

上記のコードを実行すると、以下のような結果が得られます。

自己のクラス = クラス
自己の名前 = Box

これは、クラスをカレント・オブジェクトとみなしてクラス定義を実行できることを意味し、また、メソッド定義の実行期間中はメタクラスと親クラスでメソッドが利用可能であることを意味します。

以上、Rubyのオブジェクト指向の知識をまとめてみました。Rubyのオブジェクト指向の知識については、Scripting Houseの他の関連記事も参考にしてみてください