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