🔬

Rubyのselfとmodule_functionの違いがよく分からないので色々試してみた

2020/09/27に公開

※この記事は、Qiitaに投稿した記事を一部変更したものです。
https://qiita.com/kunosu/items/5c0f5c9d5628a14be7f8


経緯

『オブジェクト指向設計実践ガイド』を読んで、
オブジェクトを生成するためのモジュール(=ファクトリー)を実際に書いてみました。
しかし、モジュール名.メソッド名の形で呼び出すにはメソッドを外部に公開する必要があり、
その方法が調べると2つありました。

  1. メソッドの宣言時、メソッド名にselfを追加する
  2. module_function :メソッド名をモジュールに追加する

本ではselfをつけていたのでそちらで書きましたが、
上記2つの違いについては分からなかったのでコードを実行して調べてみました。

この記事で書いたソースコードは下記のGistで公開しています。
https://gist.github.com/kunosu/7d51967d1a79815aadf0f32dadb50d69

環境は以下の通りです。

  • Windows10
  • Ruby 2.5

実験用のモジュール

以下のようなモジュールに対して、普通に呼び出す、include, extend したクラスに対して各メソッドを呼び出してみます。

module Test_module
	def normal_method
		# __method__ は実行中のメソッド名を文字列で返す
		puts "called #{__method__}"
	end

	def self.self_method
		puts "called #{__method__}"
	end

	def module_function_method
		puts "called #{__method__}"
	end
	module_function :module_function_method
end

試してみた結果

まずはモジュールから直接呼び出してみます。

呼び出し方法 なし self module_function
Test_module.メソッド名 ×
Test_module.normal_method
# => NoMethodError

Test_module.self_method
# => called self_method

Test_module.module_function_method
# => called module_function_method

モジュールをクラスに include した場合

呼び出し方法 なし self module_function
Test_class.メソッド名 × × ×
obj.メソッド名 × ×
obj.メソッドを呼び出すメソッド ×
class Test_class
	include Test_module

	def caller_normal_method
		normal_method
	end

	def caller_self_method
		self_method
	end

	def caller_module_function_method
		module_function_method
	end
end

Test_class.normal_method
# => NoMethodError

Test_class.self_method
# => NoMethodError

Test_class.module_function_method
# => NoMethodError

test_class = Test_class.new

test_class.normal_method
# => called normal_method

test_class.self_method
# => NoMethodError

test_class.module_function_method
# => NoMethodError

test_class.caller_normal_method
# => called normal_method

test_class.caller_self_method
# => NameError

test_class.caller_module_function_method
# => called module_function_method

モジュールをクラスに extend した場合

呼び出し方法 なし self module_function
Test_class.メソッド名 × ×
obj.メソッド名 × × ×
obj.メソッドを呼び出すメソッド × × ×
class Test_class
	extend Test_module

	def caller_normal_method
		normal_method
	end

	def caller_self_method
		self_method
	end

	def caller_module_function_method
		module_function_method
	end
end

Test_class.normal_method
# => called normal_method

Test_class.self_method
# => NoMethodError

Test_class.module_function_method
# => NoMethodError

test_class = Test_class.new

test_class.normal_method
# => NoMethodError

test_class.self_method
# => NoMethodError

test_class.module_function_method
# => NoMethodError

test_class.caller_normal_method
# => NameError

test_class.caller_self_method
# => NoMethodError

test_class.caller_module_function_method
# => NoMethodError

試してみた結果からの selfmodule_function の使い分け

※試してみた結果から得たものであり、こう使うのが正しいという訳ではありません。

  • モジュールだけで使う場合はどちらでもいい
    • ファクトリーなど
  • クラス名.メソッド名 の形で呼び出す場合は、メソッドに何も指定せず、extend して使う
    • 特異メソッドとして使う
  • obj.メソッド名 の形で呼び出す場合は、メソッドに何も指定しない
  • obj.メソッドを呼び出すメソッド の形で呼び出す場合は、次のどちらかで extend して使う
    • メソッドに何も指定しない
    • module_function を使う

参考文献

  • Sandi Metz(2016)『オブジェクト指向設計実践ガイド』髙山泰基訳, 技術評論社

Discussion