• トップ
  • ブログ一覧
  • 「Rubyはすべてがオブジェクト」を体感したい
  • 「Rubyはすべてがオブジェクト」を体感したい

    はじめに

    こんにちは。ライトコード入社2年目になる矢野と申します。
    今回は、Rubyを触っていたらよく耳にする「Rubyはすべてがオブジェクト」という文言について深掘りしていこうと思います。

    というのも、入社する前の面接で「Rubyはすべてがオブジェクトというけどどういう意味か知っていますか?」と質問されたのですが、まだ理解が浅くちゃんと答えられませんでした。

    入社して2年目に入るのでこの質問にちゃんと答えられるよう、体感しながら「Rubyはすべてがオブジェクト」を理解したいと思います。

    オブジェクト指向の基本的な考え方

    プログラムを「オブジェクト」という単位で構築していく考え方で、データとそれに対する操作(メソッド)を一つの単位として扱います。
    この考え方により、プログラムを直感的に分割・整理し、現実世界の概念をモデル化しやすくするため、システムが管理しやすくなります。
    処理やデータを"オブジェクト"としてひとまとめにすることで、保守性や拡張性を高めます。

    基本的な概念

    今回の記事に必要な概念をピックアップします。

    1. クラス
    2. オブジェクト

    1. クラス

    オブジェクトを生成するための「設計図」です。クラスがオブジェクトが持つ「データ」・「メソッド」を定義します。
    このクラス(設計図)をもとに、オブジェクトを実体化(インスタンス化)します。

    つまり、全てのオブジェクトは設計図(クラス)をもとに生成されています。

    2. オブジェクト

    プログラム内の「モノ」を指します。
    例えば、「フルーツ」、「車」、「自転車」はクラス(設計図)で、オブジェクトは、「スイカ」や「セレナ」、「マウンテンバイク」です。

    オブジェクトは、「データ(状態・属性)」と「メソッド(操作・振る舞い)」を持っています。

    「1.クラス」の章で説明した通り、データとメソッドはクラスで定義します。

    データ(状態・属性)と「メソッド(操作・振る舞い)の関係性の例

    例1)「フルーツ」クラスの「スイカ」オブジェクト
    データ(状態・属性)

    • フルーツ名:スイカ
    • 重さ:100g
    • 大きさ: 20㎠

    メソッド(操作・振る舞い)

    • 他のフルーツに触れたら、重力に従い転がる
    • スイカと触れたら消える

    例2) 「車」クラスの「セレナ」オブジェクト
    データ(状態・属性)

    • 車種:セレナ
    • 色:白
    • 車の種類:普通車

    メソッド(操作・振る舞い)

    • エンジンをつける
    • エンジンを止める
    • 発進する

    オブジェクト間の連携

    オブジェクト指向においては、オブジェクト同士が "メッセージ"をやりとりすることで協調し、プログラム全体の動作を実現するという考え方があります。ここでいうメッセージとは、「メソッドの呼び出し」を指します。

    具体的には、あるオブジェクトが別のオブジェクトに対して何らかの処理を頼みたい時、「メソッドを呼び出す」ことでそのオブジェクトへ"メッセージ"を送るイメージです。
    呼び出した側のオブジェクトは、呼ばれた別のオブジェクトのメソッドの定義に従って処理を実行し、結果を返したり、オブジェクトの状態を変化させます。これによってオブジェクト同士の連携が成立します。

    Rubyで学ぶオブジェクト指向

    Rubyでオブジェクトを生成するサンプル

    Rubyでクラスを定義する際のサンプルコードを以下に示します。

    以下のコードでは、車を表すCarクラスを定義しています。

    1class Car
    2  def initialize(name)
    3    @name = name
    4    @engine_on = false
    5  end
    6  
    7  def start_engine
    8    @engine_on = true
    9    puts "#{@name}のエンジンを起動しました"
    10  end
    11  
    12  def stop_engine
    13    @engine_on = false
    14    puts "#{@name}のエンジンを停止しました"
    15  end
    16  
    17  def status
    18    if @engine_on
    19      puts "#{@name}のエンジンは起動しています"
    20    else
    21      puts "#{@name}のエンジンは停止しています。"
    22    end
    23  end
    24end

    initializeメソッドで、オブジェクトの属性や状態を定義しています。initializeメソッドは、インスタンス生成時に自動的に呼び出されるメソッドで、インスタンスの初期値の設定や、実行したい処理を記述することができます。
    変数名に@をつけることでそのクラスの属性を定義します。
    @のついた変数はインスタンス変数と呼ばれ、インスタンスごとに異なるデータを持たせたい時に使用します。

    initializeメソッドの下にdef ... endで、メソッドを定義しています。ここではオブジェクトがどんな振る舞いするのかを定義しています。

    オブジェクトを生成

    1my_car = Car.new("セレナ")
    2
    3my_car.status        # => セレナのエンジンは停止しています
    4my_car.start_engine  # => セレナのエンジンを起動しました。
    5my_car.status        # => セレナののエンジンは起動しています
    6my_car.stop_engine   # => セレナのエンジンを停止しました。
    7my_car.status        # => セレナのエンジンは停止しています。

    オブジェクトのクラスを確認するには、classメソッドを使用します。

    1my_car.class # => Car

    my_carの設計図はCarクラスであることがわかります。

    オブジェクト間の連携

    Carクラスと連携する運転手のクラスを定義

    1class Car
    2  # ~~~~省略~~~~
    3end
    4
    5class Driver
    6  def initialize(name, age)
    7    @name = name
    8    @age = age
    9    @car = nil
    10  end
    11
    12  def assign_car(car)
    13    @car = car
    14  end
    15
    16  def drive
    17    if @car
    18      puts "#{@name}は#{@car.name}を運転してます"
    19    else
    20      puts "#{@name}は車を持っていません"
    21    end
    22  end
    23
    24  # 車のエンジンをつける
    25  def engine_on
    26    if @car
    27      @car.start_engine
    28    end
    29  end
    30end

    オブジェクト間の連携をしてみる

    Driverクラスから、Carクラスstart_engineメソッドを呼び出します。
    DriverからCarクラスに対して「車のエンジンをつける」というメッセージを送っています。

    Carクラスは「車のエンジンをつける」というメッセージを受け取り、自身のオブジェクトの状態を変化させます。
    オブジェクト指向では、オブジェクト同士が直接データを共有するのではなく、メッセージを通じて情報をやり取りすることでオブジェクト間の連携を実現させます。

    以下、DriverオブジェクトとCarオブジェクトを実際に連携させてみます。

    1driver = Driver.new("Kentaro", 28)
    2car = Car.new("セレナ")
    3
    4driver.assign_car(car) # 運転手の持つ車を決める
    5driver.drive       # => Kentaroはセレナを運転してます
    6
    7car.status             # => セレナのエンジンは停止しています
    8driver.engine_on       # => セレナのエンジンを起動しました
    9car.status             # => セレナのエンジンは起動しています

    Driverが自身のengine_onメソッド内で、Carオブジェクトのstart_engineメソッドを呼び出すことで、Carオブジェクトのstatusが変更されていることがわかります。
    このように、オブジェクト間の連携はメッセージの送受信を通して行われます。

    このメッセージのやり取りをオブジェクト指向では「メッセージパッシング」と言います。
    メッセージパッシングを行うことで、オブジェクトはその内部の状態を他のオブジェクトに公開せずに状態の変更や処理を行うことができます。
    これにより、他のオブジェクトとの依存関係が軽減され、柔軟で保守性の高い設計が可能になります。

    なぜRubyはすべてがオブジェクトと言われるのか

    Rubyでは、すべてのものはオブジェクトです。 すべての情報の塊・コードには、固有のプロパティとアクションを与えることができます。
    オブジェクト指向プログラミングでは、プロパティはインスタンス変数、アクションはメソッドと呼ばれます。

    引用元:Rubyとは

    基本的な概念で記載した、データ・属性はプロパティでアクションはメソッドと呼べます。
    典型的な例だと、数値もデータとアクションを持つことができます。

    すべてがオブジェクト

    全てがオブジェクトとはどういうことなのか、具体的な例を挙げていきます。
    様々なデータに対して、classメソッドを呼び出して、そのデータのクラスを確認していきます。

    数値もオブジェクト

    1irb(main):001> 1.class
    2=> Integer

    数値1は、Intgerクラスのオブジェクトになります。

    オブジェクトはメソッド(振る舞い・操作)を持ちます。
    メソッドは、そのオブジェクトの受け取れるメッセージです。
    Integerオブジェクトの持つメソッドを確認してみます。

    1irb(main):002> 1.methods
    2=> 
    3[:anybits?,
    4 :nobits?,
    5 :downto,
    6 :times,
    7 :pred,
    8 :pow,
    9 :**,
    10 :<=>,
    11 :<<,
    12 :>>,
    13 :<=,
    14 :>=,
    15 :==,
    16 :===,
    17 :next,
    18=== 省略 ====

    比較演算子もメソッドとして、オブジェクト:数値1が持っているようです。
    これは、IntegerクラスがRuby側で比較可能なクラスとなっているためです。

    Integerクラスがincludeしているモジュールを見てみます。

    1irb(main):003> Integer.included_modules
    2=> [Comparable, PP::ObjectMixin, Kernel]

    Integerクラスが、Comparableモジュールをincludeしていることがわかります。
    名前の通り、このモジュールをincludeすることでそのオブジェクトが比較可能になります。

    比較演算を許すクラスのための Mix-in です。

    引用元:module Comparable (Ruby 3.3 リファレンスマニュアル)

    ちなみにモジュールもModuleクラスのオブジェクトになります。

    1irb(main):004> Comparable.class
    2=> Module

    文字列もオブジェクト

    1irb(main):001> "string".class
    2=> String

    nilもオブジェクト

    nilは「値がない」「未定義」「存在しない」という意味です。データベースでいう「NULL」のようなものです。
    Rubyではこの、「何もない」という状態も「オブジェクト」です。

    1irb(main):001> nil.class
    2=> NilClass

    NilClassのオブジェクトになります。

    クラスもオブジェクト

    1class Blog
    2  def message
    3    puts "Everything is Object"
    4  end
    5end
    6p Blog.class
    7=> Class

    Classクラスのオブジェクトのオブジェクトになります。

    ブロックをオブジェクトとして扱える

    ブロックとは、Rubyのコードで一塊を表すものです。
    do ... end{}で記述されます。

    13.times do
    2  puts
    3end
    43.times { puts 'こんにちは'}

    ブロックそのものは、オブジェクトではないですが、Procを使用することでオブジェクトとして扱えます。
    Procは、Procedureの略で、日本語に翻訳すると「手続き」という意味になります。
    Procを使用することで、手続きもオブジェクトとして扱えます。

    ブロックをコンテキスト(ローカル変数のスコープやスタックフレーム)とともにオブジェクト化した手続きオブジェクトです。

    引用元:class Proc (Ruby 3.3 リファレンスマニュアル)

    Procで作成したオブジェクトはcallメソッドで呼び出します。

    1hello_proc = Proc.new { puts 'こんにちは' }
    2hello_proc.call
    3=>こんにちは

    ブロックをオブジェクトにするメリット

    1. 複数の場所で同じコードを使い回せる
    2. ブロックをメソッドや他の関数の引数として渡すことができる。
    3. 動的なメソッドの実装

    1. 複数の場所で同じコードを使い回せる

    1## nameを引数として受け取る、ブロックオブジェクトを生成
    2greeting = Proc.new { |name| puts "こんにちは、#{name}さん"
    3
    4def hello(proc)
    5  proc.call("山田一郎")
    6  proc.call("山田花子")
    7end
    8    
    9hello(greeting)
    10    
    11=>
    12こんにちは、山田一郎さん
    13こんにちは、山田花子さん

    2. ブロックをメソッドや他の関数の引数として渡すことができる

    1def other_method(proc)
    2  proc.call
    3end

    3. 動的なメソッドの実装

    1def dynamic_method(proc)
    2  proc.call("山田") # 引数として渡された手続きに引数を渡して実行
    3end
    4  
    5hello = Proc.new {|name| puts "こんにちは、 #{name}"}
    6bye = Proc.new {|name| puts "さようなら、#{name}"}
    7  
    8dynamic_method(hello)
    9dynamic_method(bye)

    以上のように手続き自体をオブジェクトとして扱うことで、より柔軟にプログラムを記述することができます。

    オブジェクトもオブジェクト

    1irb(main):001> Object.class
    2=> Class

    ObjectもClassクラスのオブジェクトです。

    Rubyでなぜすべてをオブジェクトとして扱えるかというと、「全てがオブジェクト」セクションで例として挙げている、IntegerオブジェクトやStringオブジェクトがObjectクラスを継承しているためになります。

    superclassメソッドで、オブジェクトの親を辿っていくことで継承しているクラスを確認します。

    1irb(main):001> Integer.superclass
    2=> Numeric
    3irb(main):002> Numeric.superclass
    4=> Object

    Objectクラスを継承することで、そのクラスはオブジェクトとしての振る舞いを手に入れます。

    全てのクラスのスーパークラス。オブジェクトの一般的な振る舞いを定義します。

    引用元: class Object (Ruby 3.3 リファレンスマニュアル)

    「全てのクラスのスーパークラス」とあるように、Rubyではすべてがオブジェクトであるようです。

    まとめ&感想

    Rubyでは確かにすべてがオブジェクトでした。
    ブロックのようなオブジェクトでないものも、オブジェクトとして扱う方法が用意されています。

    Rubyではすべてをオブジェクトとして扱うことで、より簡潔に現実世界の概念を表すことができます。
    また、柔軟で自由にプログラムを書くことが可能になっています。

    この柔軟性・自由度ゆえ、難しく感じることもありますが、
    Rubyの言語仕様をしっかりと理解することで、コードの読み書きが捗りそうです。
    今回の知識はRubyを業務で使う上で基礎的でかつ根幹にある部分になるなと感じました。
    ブログを機会に再確認できてよかったです。

    最後までお読みいただきありがとうございました! ではまた!!

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    やのけん(エンジニア)
    やのけん(エンジニア)
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background