クラス変数を理解しよう

学習の目標

本章では、以下の内容を学習します。

  • クラス変数の基本的な概念と特徴を理解する
  • クラス変数を使ったデータ共有の仕組みを習得する
  • クラス変数の実践的な活用方法を学ぶ
  • クラス変数を使用する際の注意点を把握する

はじめに

プログラミングにおいて、データの共有は重要な課題の一つです。例えば、アプリケーションの設定情報や統計データなど、すべてのオブジェクトで共通して使いたい値があります。

Rubyでは、このような「複数のインスタンス間でデータを共有したい」というニーズに応えるための仕組みとしてクラス変数が用意されています。

クラス変数を使いこなせると、全インスタンスで共通の値を管理したり、インスタンスの作成回数をカウントしたりといった、より高度なプログラミングが可能になります。

これまでに学んだインスタンス変数は、オブジェクトごとに独立した値を持ちます。例えば、Userクラスの各インスタンスが持つ@name@ageなどは、それぞれのユーザーによって異なる値を持ちます。

これに対して、クラス変数は「クラス全体で共有される値」を表現するものです。

クラス変数の基本

クラス変数の特徴と書き方

クラス変数は、その名の通り「クラスに属する変数」です。インスタンス変数がインスタンスごとに独立した値を持つのに対し、クラス変数はクラスのすべてのインスタンスで共有される値を持ちます。

わかりやすく例えると、インスタンス変数は「個人の所持金」のようなもので、クラス変数は「クラス全員の共有財産」のようなものと考えることができます。個人の所持金はそれぞれ別々ですが、共有財産はみんなで共有しています。そのため、誰かが共有財産を増やしたり減らしたりすると、それはみんなに影響します。

クラス変数の特徴は以下の通りです。

  • 変数名が @@ (アットマーク2つ) で始まる
  • クラス内のすべてのインスタンスで値が共有される
  • クラスメソッドからもインスタンスメソッドからもアクセスできる

基本的な使い方

それでは実際にクラス変数を使ったプログラムを作ってみましょう。VS Codeでclass_variable.rbというファイルを新規作成し、以下のコードを入力してください。

class Counter
@@count = 0 # クラス変数の初期化
def self.count
@@count # クラス変数の値を返すクラスメソッド
end
def increment
@@count += 1 # クラス変数の値を増やすインスタンスメソッド
end
end

このコードでは、Counterクラスの中で@@countというクラス変数を使っています。これはクラス全体で共有されるカウンターの役割を果たします。

具体的には以下のような特徴があります。

まず、@@count = 0 でクラス変数を初期値0で初期化しています。これはプログラムを開始した時点でのカウンターの値を0に設定するという意味です。

次に、self.countというクラスメソッドを定義しています。このメソッドはCounter.countのように、インスタンスを作らなくても呼び出せるメソッドで、現在のカウンターの値(@@countの値)を返してくれます。

最後に、incrementというインスタンスメソッドを定義しています。このメソッドはcounter.incrementのように、インスタンスに対して呼び出すメソッドで、@@countの値を1増やします。ここで重要なのは、あるインスタンスがincrementメソッドを呼び出して@@countの値を増やすと、その変更はクラス全体で共有されるという点です。

動作確認

それでは、このクラスの動作を確認するために、ファイルの末尾に以下のコードを追加してみましょう。

# Counterクラスのインスタンスを2つ作成
counter1 = Counter.new
counter2 = Counter.new
puts "初期カウント値: #{Counter.count}" # => 0 (初期値)
counter1.increment # counter1でカウントを増やす
puts "counter1でインクリメント後: #{Counter.count}" # => 1
counter2.increment # counter2でカウントを増やす
puts "counter2でインクリメント後: #{Counter.count}" # => 2
counter1.increment # もう一度counter1でカウントを増やす
puts "再びcounter1でインクリメント後: #{Counter.count}" # => 3

このコードを実行すると、以下のような結果が表示されるはずです。

初期カウント値: 0
counter1でインクリメント後: 1
counter2でインクリメント後: 2
再びcounter1でインクリメント後: 3

さて、この出力結果から何が分かるでしょうか?

まず最初に、Counter.countを呼び出したとき、値は0でした。これは@@countの初期値そのままです。

次に、counter1.incrementを実行すると、counter1というインスタンスで@@countの値が1増えて、Counter.countの結果は1になりました。

ここからが重要なポイントです。次に別のインスタンスcounter2incrementメソッドを呼び出しました。通常のインスタンス変数であれば、counter1counter2は別々の変数を持っているはずですが、ここではクラス変数@@countを使っているので、値は共有されています。そのため、Counter.countの結果は2になりました。

最後に、もう一度counter1incrementメソッドを呼び出すと、値は3になりました。

このように、クラス変数は全インスタンスで共有されるので、どのインスタンスでカウントを増やしても、同じ@@countの値が変化するのです。これがクラス変数の最も重要な特徴と言えるでしょう。

実際のプログラミングでも、「全体で一つだけ持っておきたい情報」がある場合にクラス変数は非常に役立ちます。例えば、アプリケーションの設定情報や、インスタンスの総数カウントなどがそれにあたります。

クラス変数の実践的な活用例

クラス変数は様々な場面で活用できますが、特に「全インスタンスで共有したい設定情報」や「統計情報」の管理に適しています。実践的な例として、アプリケーションの設定情報を管理するクラスを実装してみましょう。

設定情報を管理するクラス

実際のアプリケーション開発では、APIキーやタイムアウト時間など、プログラム全体で共通して使いたい設定情報がよくあります。そんなとき、クラス変数を使った設定管理クラスがとても役立ちます。

それでは、新しいファイルconfiguration.rbを作成して、以下のコードを入力してみましょう。このコードでは、アプリケーション全体の設定を管理する仕組みを作ります。

class Configuration
@@settings = {} # 設定情報を保持するハッシュ
def self.set(key, value)
@@settings[key] = value # 設定値を追加・更新するクラスメソッド
end
def self.get(key)
@@settings[key] # 設定値を取得するクラスメソッド
end
end
# 設定値を追加してみましょう
Configuration.set(:api_key, "abc123")
Configuration.set(:max_retry, 3)
# 設定値を取得して表示してみましょう
puts Configuration.get(:api_key) # => abc123
puts Configuration.get(:max_retry) # => 3

さて、このコードを詳しく見ていきましょう。

まず、Configurationクラスの中で@@settingsというクラス変数を作っています。これはハッシュ(連想配列)なので、キーと値のペアを自由に格納できます。たとえば、アプリのAPIキーや最大リトライ回数などの設定情報をここに保存できるんです。

次に、この設定情報を操作するための2つのクラスメソッドを用意しています。

  • setメソッドは、設定を追加したり更新したりするためのもので、キーと値を指定して呼び出します
  • getメソッドは、指定したキーに対応する設定値を取り出すためのものです

このコードの素晴らしいところは、わざわざインスタンスを作らなくても、Configuration.set(:api_key, "abc123")のように、クラス名を指定するだけで設定値を追加できることです。同様に、Configuration.get(:api_key)とするだけで、どこからでも設定値を参照できます。

例えば、あなたが大きなアプリケーションを開発していて、さまざまなクラスやメソッドから共通の設定値にアクセスしたいとき、このような仕組みがあれば、設定情報を一箇所で管理できて便利です。設定を変更したいときも、このクラスのsetメソッドを呼ぶだけで、アプリケーション全体に変更が反映されます。

試しに、このコードを実行してみると、ちゃんと設定した値が取得できることが確認できると思います。このように、クラス変数を活用すると、グローバルな設定情報の管理が簡単にできるようになるんですよ。

インスタンス数をカウントする例

クラス変数のもう一つの便利な使い道として、「あるクラスから何個のインスタンスが作られたか」をカウントする例を見てみましょう。これは、例えばゲームでキャラクターの人数を管理したり、アプリケーションでユーザー数を追跡したりするのに役立ちます。

それではuser_counter.rbというファイルを新しく作成し、以下のコードを入力してみましょう。

class User
@@user_count = 0 # ユーザー数を管理するクラス変数
def initialize(name)
@name = name
@@user_count += 1 # インスタンス作成時にカウントを増やす
puts "#{@name}さんを作成しました。現在のユーザー数: #{@@user_count}人"
end
def self.count
@@user_count # 現在のユーザー数を返すクラスメソッド
end
def name
@name
end
end
# ユーザーを3人作ってみましょう
puts "最初のユーザー数: #{User.count}人" # => 0人(まだ誰も作っていない)
user1 = User.new("山田") # => 山田さんを作成しました。現在のユーザー数: 1人
puts "#{user1.name}さんを作成後のユーザー数: #{User.count}人" # => 1人
user2 = User.new("佐藤") # => 佐藤さんを作成しました。現在のユーザー数: 2人
puts "#{user2.name}さんを作成後のユーザー数: #{User.count}人" # => 2人
user3 = User.new("鈴木") # => 鈴木さんを作成しました。現在のユーザー数: 3人
puts "#{user3.name}さんを作成後のユーザー数: #{User.count}人" # => 3人

このコードは、Userクラスのインスタンス(ユーザー)が作られるたびに、そのカウントを自動的に増やす仕組みになっています。具体的には以下のような流れです。

まず、クラスの定義の最初に@@user_count = 0として、クラス変数を0で初期化しています。これは「まだユーザーは1人も作られていない」という状態を表しています。

次に、initializeメソッド(コンストラクタ)の中で、ユーザーが新しく作られるたびに@@user_count += 1として、カウントを1つ増やしています。このクラス変数はクラス全体で共有されるので、新しいユーザーが作られるたびにカウントが積み上がっていきます。

また、self.countクラスメソッドを定義していますので、User.countと呼び出すだけで現在のユーザー数を簡単に取得できます。便利ですね!

実際にこのコードを実行してみると、ユーザーが作られるたびにカウントが増えていくことが確認できると思います。最初は0人、1人目を作ると1人、2人目を作ると2人...というように増えていきます。

これがクラス変数のいいところで、個々のインスタンスは独立していても、クラス全体に関わる情報(この場合は「総ユーザー数」)を簡単に管理できるんです。

実際のアプリケーション開発でも、このような「全体の状態」を管理するためにクラス変数はよく使われます。

クラス変数使用時の注意点

クラス変数は強力な機能ですが、使用する際には注意が必要な点がいくつかあります。ここでは、クラス変数を使う上で特に気をつけたい3つのポイントを説明します。

すべてのインスタンスで値が共有される

クラス変数の値はクラスのすべてのインスタンスで共有されます。これはメリットでもありますが、意図しない値の変更が発生する可能性もあります。

例えば、次のような例を考えてみましょう。

class BankAccount
@@interest_rate = 0.01 # 金利(クラス変数)
def initialize(name, balance)
@name = name
@balance = balance
puts "#{@name}さんの口座を作成しました。残高:#{@balance}円"
end
def self.interest_rate
@@interest_rate
end
def self.interest_rate=(new_rate)
@@interest_rate = new_rate
end
def add_interest
interest = @balance * @@interest_rate
@balance += interest
puts "#{@name}さんの口座に利息#{interest}円を追加しました。新しい残高:#{@balance}円"
end
end
# 2つの口座を作成
account1 = BankAccount.new("山田", 10000) # => 山田さんの口座を作成しました。残高:10000円
account2 = BankAccount.new("佐藤", 20000) # => 佐藤さんの口座を作成しました。残高:20000円
# 金利を確認して利息を追加
puts "現在の金利: #{BankAccount.interest_rate * 100}%" # => 現在の金利: 1.0%
account1.add_interest # => 山田さんの口座に利息100円を追加しました。新しい残高:10100円
account2.add_interest # => 佐藤さんの口座に利息200円を追加しました。新しい残高:20200円
# 金利を変更(すべての口座に影響)
BankAccount.interest_rate = 0.02
puts "金利を変更しました。新しい金利: #{BankAccount.interest_rate * 100}%" # => 金利を変更しました。新しい金利: 2.0%
# 再度利息を追加すると、すべての口座で新しい金利が適用される
account1.add_interest # => 山田さんの口座に利息202円を追加しました。新しい残高:10302円
account2.add_interest # => 佐藤さんの口座に利息404円を追加しました。新しい残高:20604円

この例では、銀行口座の金利をクラス変数@@interest_rateで管理しています。金利は銀行全体で共通のものなので、クラス変数を使うのは理にかなっています。

しかし、このような共有の仕組みは時に問題を引き起こします。例えば、大規模なアプリケーションで複数の箇所からクラス変数を変更する場合、どこで変更されたのかを追跡するのが難しくなります。また、テストの際にある箇所でクラス変数を変更すると、他のテストに影響を及ぼす可能性があります。

継承時の挙動に注意

Rubyでは、クラス変数は継承されたサブクラスとも共有されます。これは思わぬ副作用をもたらすことがあります。

次の例で確認してみましょう。

class Parent
@@value = "親の値"
def self.value
@@value
end
def self.value=(new_value)
@@value = new_value
end
end
class Child < Parent
# 特に何も定義していない
end
# 親クラスの値を確認
puts "Parent.value: #{Parent.value}" # => Parent.value: 親の値
# 子クラスも同じ値を共有している
puts "Child.value: #{Child.value}" # => Child.value: 親の値
# 子クラスで値を変更すると...
Child.value = "子の値"
puts "Child.value = \"子の値\" を実行"
# 親クラスの値も変わってしまう!
puts "Parent.value: #{Parent.value}" # => Parent.value: 子の値
puts "Child.value: #{Child.value}" # => Child.value: 子の値

このコードを実行すると、子クラスChildでクラス変数の値を変更すると、親クラスParentの値も変わってしまうことがわかります。これは、クラス変数が継承階層全体で共有されるためです。

この挙動は意図しない場合も多いので、継承関係のあるクラスでクラス変数を使う場合は特に注意が必要です。もし親クラスと子クラスで別々の値を持たせたい場合は、クラス変数ではなく「クラスインスタンス変数」(@で始まる変数をクラスメソッド内で使う方法)を検討するとよいでしょう。

テスト時の注意点

テストを実行する際、クラス変数の値はテスト間で共有されるため、テストの順序によって結果が変わる可能性があります。これはテストの信頼性を損なう原因になります。

例えば、次のようなテストケースを考えてみましょう。

# テスト1
def test_counter_increment
Counter.reset # カウンターをリセット
counter = Counter.new
counter.increment
assert_equal 1, Counter.count
end
# テスト2
def test_counter_decrement
counter = Counter.new
counter.decrement
assert_equal -1, Counter.count
end

もしCounterクラスのカウンターがクラス変数で実装されていて、かつテスト1とテスト2が順番に実行される場合、テスト2は失敗する可能性があります。なぜなら、テスト1でカウンターが1に増やされた後、テスト2ではその値が引き継がれてしまうからです。

このような問題を避けるためには、各テストの前後で適切にクラス変数の値をリセットするメソッドを用意するとよいでしょう。例えば、上記の例ではCounter.resetのようなメソッドを使っています。

class Counter
@@count = 0
def self.reset
@@count = 0 # カウンターをリセットするクラスメソッド
end
# ...他のメソッド...
end

これらの注意点を理解しておくことで、クラス変数を適切に活用できるようになります。クラス変数は強力なツールですが、その特性を十分に理解した上で使いましょう。まいます。これが意図した動作でなければ、代わりに@で始まる「クラスインスタンス変数」を使用することを検討してもよいでしょう。

テスト時の注意点

テストを実行する際、クラス変数の値はテスト間で共有されるため、テストの順序によって結果が変わる可能性があります。テストケースでクラス変数を使用する場合は、各テストの前後で適切にクラス変数の値をリセットする必要があります。

まとめ

この章では、Rubyのクラス変数について学びました。クラス変数は、オブジェクト指向プログラミングにおける共有データを扱うための重要な仕組みです。

ここで学んだ主なポイントを振り返ってみましょう。

  • クラス変数は@@で始まり、クラスのすべてのインスタンスで値を共有します
  • クラスメソッドとインスタンスメソッドの両方からクラス変数にアクセスできます
  • クラス変数は設定情報の管理やインスタンス数のカウントなど、共有データの管理に適しています
  • クラス変数を使用する際は、値の共有による思わぬ影響や継承時の挙動に注意が必要です

クラス変数は、プログラム全体で一貫性のある情報を扱うのに非常に便利です。例えば、アプリケーション全体の設定情報や統計データなど、「全体で一つだけ」持ちたい情報を管理するのに最適です。

一方で、その「共有される」という特性から、クラス変数を使う際には注意も必要です。特に、大規模なアプリケーションや継承関係が複雑な場合は、クラス変数の使用を慎重に検討しましょう。

クラス変数の長所と短所を理解した上で、適切なシーンで活用していくことが大切です。オブジェクト指向プログラミングでは、インスタンス変数、クラス変数、グローバル変数などさまざまな種類の変数がありますが、それぞれの特性を理解し、目的に合った変数を選ぶことで、より良いプログラムを書くことができます。

次の章では、プログラムの実行時に予期せぬエラーが発生した場合に対処する「例外処理」について学んでいきます。それまでの間に、ぜひこの章で学んだクラス変数を使って、自分でもいろいろなプログラムを書いてみてください。

このセクションは有料サブスクリプションへの登録、またはログインが必要です。完全なコンテンツにアクセスするには、料金ページ(/pricing)をご覧ください。購入済みの場合は、ログインしてください。

Starterプランでより詳しく学習

この先のコンテンツを読むにはStarterプラン以上が必要です。より詳細な解説、実践的なサンプルコード、演習問題にアクセスして学習を深めましょう。

作成者:とまだ
Previous
クラスメソッドでインスタンスを作らずにメソッドを使おう