Ruby のクラスはオープン
Ruby のクラスはオープンなんです. どういうことですかって?
つまりは, あらかじめ定義されている Ruby のクラスに後から好きなようにメソッドを追加で定義できるんです.
この機能は使いようによってはとても便利なのです.
有用性の実証
例えば String クラスでその有用性を実証してみたいと思います.
def from_foo_to_bar(text)
text.gsub('foo', 'bar')
end
text = 'foo foo bar bar'
puts from_foo_to_bar(text).upcase.inspect # "BAR BAR BAR BAR"
foo
を bar
に変換する from_foo_to_bar
という関数を定義して, それを呼び出し , 返した文字列を upcase
メソッドで大文字に変換しています.
このようなコードを Ruby のクラスはオープンという機能を利用すると次のように書き換えることができます:
class String
def from_foo_to_bar
self.gsub('foo', 'bar')
end
end
text = 'foo foo bar bar'
puts text.from_foo_to_bar.upcase.inspect # "BAR BAR BAR BAR"
どうでしょうか.
text
は文字列なので, String クラスのインスタンスオブジェクトです:
puts text.class # String
なので String クラスのインスタンスメソッドとして from_foo_to_bar
を定義することによって, text.from_foo_to_bar
のように他の String メソッドを呼び出すときのように呼び出すことができます.
self.gsub
のように self
で String のインスタンスオブジェクトを参照しているところがポイントです.
この場合は text
なので, self.gusb
というのは, 内部的には text.gsub
と同等のことが行われています.
最初の例のように from_foo_to_bar(text)
のように呼び出す方法もいいですが, 使用できるオブジェクトを String に絞った text.from_foo_to_bar
の方が少しいいのではないでしょうか.
Ruby のローカルスコープに定義するのではなく, String クラスのインスタンススコープに定義するので, 影響を抑えることができますし.
またメソッドチェインと呼ばれるオブジェクトのインスタンスメソッドを繋げて元のオプジェクトの値を徐々に編集していく方法とも相性がいいです.
このように既存のクラスで新たな自分だけのインスタンスメソッドを追加したいという場合は, そのクラスを定義し, そのクラス内に自分が定義したいインスタンスメソッドを定義すれば, そのインスタンスメソッドが既存のクラスに追加され, 使えるようになります .
つまりは既存のクラスに新たな機能を追加して, 既存のクラスの機能を拡張したいという場合に使われる方法です.
Rails でも使われている
この方法はとてもポピュラーなので, Rails などでも使われています.
例えば Rails の Time クラスは advanced
というインスタンスメソッドが既存の Time クラスにこの方法で追加されています.
次のコードをみていただけると:
advance
というインスタンスメソッドが Time クラスに定義されているのがわかります .
ただ実際のコードはそのようにやや複雑ですが, 既存のクラスを再定義してその中にインスタンスメソッド定義するという手順は一貫しています.
Array や Hash でも試してみる
先ほどは String クラスで試してみましたが, 折角なので Array や Hash でも同じようにインスタンスメソッドを追加して試してみたいと思います.
Array の場合
class Array
def from_foo_to_bar
self.map do |element|
element == 'foo' ? 'bar' : element
end
end
end
array = ['foo', 'foo', 'bar', 'bar']
puts array.from_foo_to_bar.inspect # => ["bar", "bar", "bar", "bar"]
先ほどと同じように Array クラスを定義し, その中に from_foo_to_bar
を定義しました.
そして self
で Array のインスタンスオブジェクトを参照し, map
で要素が foo
と一致するものは bar
に変更し, それ以外の要素はそのままにして新たな配列を作り, それを返します.
便利ですよねこの機能.
Hash の場合
class Hash
def from_foo_to_bar
self.to_a.map do |(key, value)|
value = 'bar' if value == 'foo'
[key, value]
end.to_h
end
end
hash = { 1 => 'foo', 2 => 'foo', 3 => 'bar', 4 => 'bar' }
puts hash.from_foo_to_bar.inspect # => {1=>"bar", 2=>"bar", 3=>"bar", 4=>"bar"}
同じように Hash クラスを定義し, その中に from_foo_to_bar
メソッドを定義し, self
で hash
を参照し, まず to_a
で Hash から Array に変換してから, それぞれの要素を map
でまわり, (key, value)
で第一要素を key
, 第二要素を value
として割り当て, value
が foo
なら bar
を代入しています.
そして新しく作り変えられた配列を to_h
メソッドで Hash に戻して, その値を返します.
こんな感じでどんなクラスにも使えてしまうのですよ.
まとめ
今回は, Ruby のクラスはオープンということでしたが, そのオープンさと利点を知っていただけたのではないでしょうか.
今までのような感じで, String, Array, Hash クラスに限らず, どんな既存のクラスでもいくらでも拡張することができますので, Ruby を使われている方だったらぜひ知っておきたいテクニックです.
そのぐらい便利な機能ですので, ぜひ知っていただけたらと思いそのような内容を今回は書かせていただきました.
最後までお読みくださいましてありがとうございました.
関連記事
Ruby のメソッドチェインって便利で楽しい2018.05.04
Ruby の "shadowing outer local variable" という警告について2018.06.16
Ruby の正規表現を備忘録としてまとめてみた2018.08.30
Ruby の関数プログラミングでオイラー積を計算してみた2018.07.02
Ruby のグローバル変数をそれぞれ調べてみた2018.06.12