- 更新日: 2015年7月31日
- Ruby
Rubyでクラスインスタンス変数にインスタンスメソッドからアクセス
Ruby では、クラス変数にはインスタンスメソッドからアクセス可能です。一方クラスインスタンス変数には、通常はインスタンスメソッドからアクセスすることができません。しかし一見できそうにないことでも殆どの場合抜け道が用意されているのが Ruby なわけで、Ruby でクラスインスタンス変数にインスタンスメソッドからアクセスする方法を考えました。
Ruby のクラス変数とクラスインスタンス変数の違い
Ruby でクラス自身に変数を保持させたい場合には、クラス変数かまたはクラスインスタンス変数のどちらかを使う方法が用意されている。そして、上述した以外には Ruby でのクラス変数とクラスインスタンス変数には以下のような違いがあります。
・Ruby でクラス変数(@@hoge)はサブクラスからも参照できる。
・クラスインスタンス変数はサブクラスからは参照できない。クラスインスタンス変数は、クラス定義のスコープ(self がクラス自身)で @hoge(@ が1つ)と書いて定義できます。
Ruby – 【まとめ】インスタンス変数、クラス変数、クラスインスタンス変数 – Qiita
クラス変数とクラスインスタンス変数とインスタンス変数の違いについて – 遅咲きのエンジニア
Rubyでのクラス変数、インスタンス変数、クラスインスタンス変数の違いについて – simanmanのブログ
上記ページなどが、クラス変数とクラスインスタンス変数の違いについて分かりやすいです。そして、クラス自身に変数を保持させたい場合にどちらを使うべきかはケースバイケースで、継承クラスでも共有したい場合はクラス変数を使い、継承クラスで共有されたくない場合はクラスインスタンス変数を使うのだろうと思います。
以上を踏まえて、メタプログラミング Ruby(p146)を読んで確認してみましたところ…
先ほど説明したひどい癖が原因で、多くの Rubyist はクラス変数を使わずにクラスインスタンス変数を使っている。メタプログラミング Ruby(p146)
とのことです。
先ほど説明したひどい癖とは、クラス変数が継承されることによる以下のような Ruby の性質のこと。
1 2 3 4 5 6 7 8 |
irb> @@var = 0 => 0 irb> class Hoge irb> @@var = 1 irb> end => 1 irb> @@var => 1 |
Ruby のトップレベルのスコープでは、self は main(Object クラスのインスタンス)となるので、上記の @@var は Object に属しており、Hoge は Object を継承しているので @@var を書き換えてしまう。トップレベルのスコープでクラス変数を定義することは通常ないと思いますけど、この場合ですとほぼグローバル変数のような振る舞いです。
なので、クラス自身に変数を持たせたい場合は、基本的にはクラスインスタンス変数を使うようにしたほうが良さそう。
Object#instance_variable_get, Object#instance_variable_set を使って解決
前置きが長くなってしまいましたけどここから本題。クラス変数じゃなくてクラスインスタンス変数を使うようにしようと考えたわけですけど、次に問題になるのがインスタンスメソッドからクラスインスタンス変数にアクセスできない点。インスタンスメソッドからクラスインスタンス変数を操作しようとした時に、どうするべきか?少し考えまして、以下の方法で解決しました。
Ruby でカプセル化されているインスタンス変数に外部からアクセスするには、Object#instance_variable_get, Object#instance_variable_set を利用することができます。クラスインスタンス変数とは、文字通りクラスのインスタンス変数なので、インスタンスメソッド内からクラス名に対してこれらのメソッドを呼び出せば、クラスインスタンス変数にアクセス可能です。
インスタンスメソッドからクラスインスタンス変数にアクセスする Ruby コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class C # class instance variable @count = 0 def get_count C.instance_variable_get(:@count) end def add_count count = C.instance_variable_get(:@count) count += 1 C.instance_variable_set(:@count, count) end end c = C.new c.get_count # => 0 c.add_count # => 1 c.get_count # => 1 c.add_count # => 2 c.get_count # => 2 another_c = C.new another_c.get_count # => 2 another_c.add_count # => 3 another_c.get_count # => 3 |
コードの動作は irb で確認しましたが、上記のようにインスタンスメソッドからクラスインスタンス変数を操作することができました。
インスタンスメソッド内のクラス名の定数を消す
インスタンスメソッド定義の中で、C というクラス名の定数が何度も使われるのはちょっと嫌な感じです…。ということで、クラス名の定数を使わないようにする書き方です。define_method によるフラットスコープを利用しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class D # class instance variable @count = 0 klass = self define_method(:get_count) do klass.instance_variable_get(:@count) end define_method(:add_count) do count = klass.instance_variable_get(:@count) count += 1 klass.instance_variable_set(:@count, count) end end d = D.new d.get_count # => 0 d.add_count # => 1 d.get_count # => 1 d.add_count # => 2 d.get_count # => 2 another_d = D.new d.get_count # => 2 d.add_count # => 3 d.get_count # => 3 |
これでクラス名の定数 D を消せました。
クラスメソッドを通してインスタンスメソッドからアクセス
もう1つ、クラスメソッドを通して、インスタンスメソッドからクラスインスタンス変数を操作する方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class E # class instance variable @count = 0 # class methods class << self def get_count @count end def add_count @count += 1 end end # instance methods def get_count E.get_count end def add_count E.add_count end end e = E.new e.get_count # => 0 e.add_count # => 1 e.get_count # => 1 e.add_count # => 2 e.get_count # => 2 another_e = E.new e.get_count # => 2 e.add_count # => 3 e.get_count # => 3 |
クラスメソッドからはクラスインスタンス変数にアクセス可能です。なので、クラスメソッドの定義が必要になってしまいますけど、一応この方法でもクラスインスタンス変数を、クラスメソッドを介してインスタンスメソッドから操作できます。インスタンスメソッド内のクラス名の定数 E を消したい場合は、前述した define_method によるフラットスコープを利用すればOKです。
以上のような方法でクラスインスタンス変数にインスタンスメソッドからアクセスすることができました。あまり確固たる自信があるわけでもないので、他にクラスインスタンス変数にインスタンスメソッドからアクセスする良い書き方がありましたら、ぜひご教授ください。
- Ruby の関連記事
- Gemの作り方(Ruby Gem)
- ローカル開発中のgemをGemfileに書いてインストール
- 熊本地震の余震が夜に多いのは本当か?Rubyプログラムで検証してみた
- El Capitanでgemのnative extensionビルド失敗に対応
- Rubyで親クラスから子クラスの定数を参照
- MacabをRubyで使う
- rbenv/ruby-buildでRuby最新バージョンをインストール
- 距離1kmあたりの緯度・経度の度数を計算(日本・北緯35度)
- Google Maps Geocoding APIで住所から緯度・経度を取得するRubyコード
- Yahoo地図API(YOLP)のジオコーダAPIで住所から緯度・経度を求めるRubyコード
- 2件のコメント
クラスメソッドを通してアクセスする方法では、特異クラスの中に attr_accessor 等でアクセサメソッドを定義してもいいですね。
あと、クラス名の定数を消す方法としてはインスタンスメソッド側で”self.class.クラスメソッド”を使う方法もあります。
class E
# class instance variable
@count = 0
# class methods
class << self
attr_accessor :count
end
# instance methods
def get_count
self.class.count
end
def add_count
self.class.count += 1
end
end
> 特異クラスの中に attr_accessor 等でアクセサメソッドを定義…
これは思いつきませんでした。ありがとうございました m(_ _)m。