Rubyでクラスインスタンス変数にインスタンスメソッドからアクセス

スポンサーリンク

Ruby では、クラス変数にはインスタンスメソッドからアクセス可能です。一方クラスインスタンス変数には、通常はインスタンスメソッドからアクセスすることができません。しかし一見できそうにないことでも殆どの場合抜け道が用意されているのが Ruby なわけで、Ruby でクラスインスタンス変数にインスタンスメソッドからアクセスする方法を考えました。

【お知らせ】 英単語を画像イメージで楽に暗記できる辞書サイトを作りました。英語学習中の方は、ぜひご利用ください!
画像付き英語辞書 Imagict | 英単語をイメージで暗記
【開発記録】
英単語を画像イメージで暗記できる英語辞書サービスを作って公開しました
スポンサーリンク

Ruby のクラス変数とクラスインスタンス変数の違い

Ruby でクラス自身に変数を保持させたい場合には、クラス変数かまたはクラスインスタンス変数のどちらかを使う方法が用意されている。そして、上述した以外には Ruby でのクラス変数とクラスインスタンス変数には以下のような違いがあります。

・Ruby でクラス変数(@@hoge)はサブクラスからも参照できる。
・クラスインスタンス変数はサブクラスからは参照できない。クラスインスタンス変数は、クラス定義のスコープ(self がクラス自身)で @hoge(@ が1つ)と書いて定義できます。

Ruby – 【まとめ】インスタンス変数、クラス変数、クラスインスタンス変数 – Qiita
クラス変数とクラスインスタンス変数とインスタンス変数の違いについて – 遅咲きのエンジニア
Rubyでのクラス変数、インスタンス変数、クラスインスタンス変数の違いについて – simanmanのブログ

上記ページなどが、クラス変数とクラスインスタンス変数の違いについて分かりやすいです。そして、クラス自身に変数を保持させたい場合にどちらを使うべきかはケースバイケースで、継承クラスでも共有したい場合はクラス変数を使い、継承クラスで共有されたくない場合はクラスインスタンス変数を使うのだろうと思います。

以上を踏まえて、メタプログラミング Ruby(p146)を読んで確認してみましたところ…

先ほど説明したひどい癖が原因で、多くの Rubyist はクラス変数を使わずにクラスインスタンス変数を使っている。
メタプログラミング Ruby(p146)

とのことです。

先ほど説明したひどい癖とは、クラス変数が継承されることによる以下のような Ruby の性質のこと。

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 コード

コードの動作は irb で確認しましたが、上記のようにインスタンスメソッドからクラスインスタンス変数を操作することができました。

インスタンスメソッド内のクラス名の定数を消す

インスタンスメソッド定義の中で、C というクラス名の定数が何度も使われるのはちょっと嫌な感じです…。ということで、クラス名の定数を使わないようにする書き方です。define_method によるフラットスコープを利用しました。

これでクラス名の定数 D を消せました。

クラスメソッドを通してインスタンスメソッドからアクセス

もう1つ、クラスメソッドを通して、インスタンスメソッドからクラスインスタンス変数を操作する方法。

クラスメソッドからはクラスインスタンス変数にアクセス可能です。なので、クラスメソッドの定義が必要になってしまいますけど、一応この方法でもクラスインスタンス変数を、クラスメソッドを介してインスタンスメソッドから操作できます。インスタンスメソッド内のクラス名の定数 E を消したい場合は、前述した define_method によるフラットスコープを利用すればOKです。

以上のような方法でクラスインスタンス変数にインスタンスメソッドからアクセスすることができました。あまり確固たる自信があるわけでもないので、他にクラスインスタンス変数にインスタンスメソッドからアクセスする良い書き方がありましたら、ぜひご教授ください。

スポンサーリンク
私は以下の本で Ruby を覚えました。メタプログラミングRubyは入門を超える内容で難しめです。
 
スポンサーリンク
  • 2件のコメント
  • @takaakikasai

    クラスメソッドを通してアクセスする方法では、特異クラスの中に 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

  • taka

    > 特異クラスの中に attr_accessor 等でアクセサメソッドを定義…
    これは思いつきませんでした。ありがとうございました m(_ _)m。

Leave Your Message!