はじめに

この記事は書籍「プロを目指す人のためのRuby入門」に掲載できなかったトピックを著者自らが紹介するアドベントカレンダーの18日目です。
本文に出てくる章番号や項番号は書籍の中で使われている番号です。

今回はself.classでクラスメソッドを呼び出すときの注意点を説明します。

必要な前提知識

「プロを目指す人のためのRuby入門」の第7章まで読み終わっていること。

self.classの形でクラスメソッドを呼び出すときの注意点

7.5.3項ではクラスメソッドをインスタンスメソッドから呼び出す場合、次のように「self.class.クラスメソッド名」のような形で呼び出せることを説明しました。

class Product
  attr_reader :name, :price

  def initialize(name, price)
    @name = name
    @price = price
  end

  # 金額を整形するクラスメソッド
  def self.format_price(price)
    "#{price}円"
  end

  def to_s
    # self.class.クラスメソッド名の形でクラスメソッドを呼び出す
    formatted_price = self.class.format_price(price)
    "name: #{name}, price: #{formatted_price}"
  end
end

ただし、サブクラスでも同じ名前のクラスメソッドを定義した場合は、self.classだと思いがけない結果になることがあるので注意が必要です。

以下のコードではProductクラスのサブクラスであるDVDクラスで、format_priceという同じ名前のクラスメソッドを定義したときの実行例です。

class Product
  # 省略

  def self.format_price(price)
    "#{price}円"
  end

  def to_s
    # self.classが返すクラスに注意!
    formatted_price = self.class.format_price(price)
    "title: #{title}, price: #{formatted_price}"
  end
end

class DVD < Product
  # 親クラスと同名のクラスメソッドを定義する
  def self.format_price(price)
    "#{price}ドル"
  end
end

product = Product.new('A great movie', 1000)
# 円で表示される
product.to_s #=> "title: A great movie, price: 1000円"

dvd = DVD.new('An awesome movie', 3000)
# ドルで表示される
dvd.to_s     #=> "title: An awesome movie, price: 3000ドル"

ご覧のとおり、ProductとDVDで表示される文字列が異なりました。この理由は次の通りです。

self.class.format_priceと書いた場合、self.classの返す値はオブジェクトによって異なります。ProductクラスのオブジェクトであればProductが返りますが、DVDクラスのオブジェクトであればDVDが返ります。そのため、Productクラスのformat_priceが呼ばれたり、DVDクラスのformat_priceが呼ばれたりするのです。その結果としてProductは「円」で表示され、DVDは「ドル」で表示されました。

一方、次のように具体的なクラス名を書いていた場合は常にProductクラスのformat_priceメソッドが呼ばれるので、DVDも「円」で表示されるようになります。

def to_s
  # クラス名をベタ書きしたので、DVDクラスのformat_priceが呼ばれることはない
  formatted_price = Product.format_price(price)
  "title: #{title}, price: #{formatted_price}"
end

親クラスとサブクラスで同じ結果になるべきか、異なる結果になるべきかは要件によって変わってくるので、一概にどちらが正解ということはできません。ただし、一般論としてインスタンスメソッドに比べるとクラスメソッドは継承の機能を積極的に活用するケースは少ないと思います。どちらかというと、「偶然同じメソッド名を定義してしまった」というケースの方が多いのではないでしょうか。もしそうであれば、self.classと書いて結果が変わるのは不具合になってしまいます。というわけで、「どっちを選んでもよい」という状況なのであれば、Product.format_priceのように具体的なクラス名を書いてクラスメソッドを呼び出す方が、より安全かもしれません。

次回予告

次回はStructクラスの使い方を紹介します。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.