Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

この記事は書籍「プロを目指す人のための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クラスの使い方を紹介します。

jnchito
SIer、社内SEを経て、ソニックガーデンに合流したプログラマ。 「プロを目指す人のためのRuby入門」の著者。 http://gihyo.jp/book/2017/978-4-7741-9397-7 および「Everyday Rails - RSpecによるRailsテスト入門」の翻訳者。 https://leanpub.com/everydayrailsrspec-jp
https://blog.jnito.com/
sonicgarden
「お客様に無駄遣いをさせない受託開発」と「習慣を変えるソフトウェアのサービス」に取り組んでいるソフトウェア企業
http://www.sonicgarden.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした