webエンジニアの日常

RubyやPython, JSなど、IT関連の記事を書いています

クラス変数の罠。これからはクラスインスタンス変数を使おう

Effective Rubyの勉強メモです。

インスタンス変数とクラス変数

クラス内で@が先頭につく変数はインスタンス変数を表します。

インスタンス変数はオブジェクト一つ一つが持つプライベートな変数です。オブジェクトのあるインスタンス変数に値をセットしてもほかのオブジェクトのインスタンス変数には影響を及ぼしません。

特別な変数にクラス変数というものもあります。@@が先頭につく変数で、クラスで一つの値を持ち、そのクラスのインスタンスすべてで共有されます。

もちろん、クラス変数の値を更新すると、すべてのインスタンス変数が更新後の値を使うようになります。

クラス変数を使う一般的な用途の一つにシングルトンがあります。

スポンサーリンク

クラス変数を使ってシングルトンを実装する例

シングルトンとは、システム全体にわたってただ一つのインスタンスしか存在しないようにするデザインパターンです。

例えば設定やデータベースなどを表すインスタンスです。ある個所で設定を変えるとほかの場所でも変更後の設定を見たいですよね。

こんなときに複数のインスタンスがコード中に存在していると、変更されていない古い設定を使うことになります。このような場合に、ただ一つのインスタンスを見るようにシングルトンパターンにするのです。

では実際にクラス変数を使ってシングルトンを実装してみたいと思います。

class Singleton
  private_class_method(:new, :dup, :clone)
  def self.instance
    @@instance ||= new
  end
end

def self.instanceによってクラスメソッドが定義されています。このメソッドは、インスタンスが存在しなければnewメソッドでインスタンスを作りクラス変数に入れ、存在していればそのインスタンスを返すメソッドです。

また、外部から新しくインスタンスを作られないように、新しくインスタンスを作るメソッドをprivate_class_methodメソッドでプライベートメソッド化しています。

これでシングルトンの金型ができました。

では、Singletonクラスを継承して、設定クラスとデータベースクラスを実装していきます。

class Configuration < Singleton
end

class Database < Singleton
end

それぞれのクラスのinstanceメソッドを使ってみます

Configuration.instance #=> #<Configuration:0x00000003442c28>
Database.instance #=> #<Configuration:0x00000003442c28>

ここで想定とは異なる動きをしています。

Database.instanceがConfigurationクラスのクラス変数を使っているのです。

ちなみに、この状態で

Singleton.instance #=> #<Configuration:0x00000003442c28>

となってしまいます。

つまり、クラス変数は親クラスを通して兄弟クラス(親が同じクラス)にまで伝搬してしまうのです。

クラスインスタンス変数

そこで対応策としてクラスインスタンス変数を導入しましょうということです。

以前の記事にも書きましたが、クラスもClassクラスのインスタンスなのでインスタンス変数を持つことができます。

class Singleton
  private_class_method(:new, :dup, :clone)
  def self.instance
    @instance ||= new
  end
end

4行目が変わりました。

クラスが持つインスタンス変数なのでクラスインスタンス変数と呼びます。

インスタンス変数なのでもちろん別のクラスとはプライベートになっており、伝搬することもありません。

Effective Ruby

Effective Ruby

Ruby on Rails 5アプリケーションプログラミング

Ruby on Rails 5アプリケーションプログラミング