はじめに
通常のクラス定義とは別にClass.new
という書き方でもクラスを定義できることを知りました。
この記事では通常のクラス定義とClass.new
でクラス定義した場合の挙動の違いについてまとめます。
バージョンはRuby 3.3です。
Class.new
を用いたクラス定義のやり方
Class.new
を使用してクラスを定義する方法は、通常のクラス定義とは少し異なります。
以下に、両方のやり方を比較しながら説明します。
なおこの記事では通常の方法で定義されるクラスをNormalClass
、Class.new
を用いた方法で定義されるクラスをDynamicClass
と命名していきます。
基本的なクラス定義
Class.new
でクラス定義する場合、do ... end
のブロックを渡してその中でクラスの中身を記述します。
クラスの使い方はどちらも同じです。
通常のクラス定義:
class NormalClass
attr_accessor :name
def initialize(name)
@name = name
end
def greet
puts "Hello, #{name}!"
end
end
# 使用例
normal_obj = NormalClass.new("Taro")
normal_obj.greet #=> Hello, Taro!
Class.new
を使用したクラス定義:
DynamicClass = Class.new do
attr_accessor :name
def initialize(name)
@name = name
end
def greet
puts "Hello, #{name}!"
end
end
# 使用例
dynamic_obj = DynamicClass.new("Taro")
dynamic_obj.greet #=> Hello, Taro!
メソッドの定義
メソッドの定義は通常と同じようにできます。
通常のクラス定義:
class NormalClass
def self.class_method
puts "これはクラスメソッドです"
end
def instance_method
puts "これはインスタンスメソッドです"
end
end
# クラスメソッド
NormalClass.class_method #=> これはクラスメソッドです
# インスタンスメソッド
normal_obj = NormalClass.new
normal_obj.instance_method #=> これはインスタンスメソッドです
Class.new
を使用したクラス定義:
DynamicClass = Class.new do
def self.class_method
puts "これはクラスメソッドです"
end
def instance_method
puts "これはインスタンスメソッドです"
end
end
# クラスメソッド
DynamicClass.class_method #=> これはクラスメソッドです
# インスタンスメソッド
dynamic_obj = DynamicClass.new
dynamic_obj.instance_method #=> これはインスタンスメソッドです
継承の指定
Class.new
でクラスを継承する場合、new
メソッドの引数に継承元のクラス名を渡すことで実現可能です。
通常のクラス定義:
class Animal
def speak
puts "動物が鳴きます"
end
end
class Dog < Animal
def speak
puts "ワン!"
end
end
# 使用例
animal = Animal.new
animal.speak #=> 動物が鳴きます
dog = Dog.new
dog.speak #=> ワン!
Class.new
を使用したクラス定義:
Animal = Class.new do
def speak
puts "動物が鳴きます"
end
end
Dog = Class.new(Animal) do
def speak
puts "ワン!"
end
end
# 使用例
animal = Animal.new
animal.speak #=> 動物が鳴きます
dog = Dog.new
dog.speak #=> ワン!
通常のクラス定義とClass.new
によるクラス定義の違い
通常のクラス定義とClass.new
によるクラス定義は主に以下の3点で違いがあります。
- スコープ
- 定数の扱い
- 動的なクラス定義
順番に説明していきます。
1. スコープ
通常のクラス定義とClass.new
によるクラス定義では、スコープの扱いが異なります。
通常のクラス定義ではクラスの外で定義された変数を参照できません。
しかしClass.new
で定義した場合はクラスの中からクラスの外で定義された変数を参照できます。
通常のクラス定義:
x = 1
class NormalClass
puts x #=> undefined local variable or method `x' for class NormalClass (NameError)
end
Class.new
を使用したクラス定義:
x = 1
DynamicClass = Class.new do
puts x #=> 1
end
2. 定数の扱い
通常のクラス定義では、クラス名が自動的に定数として定義されますが、Class.new
を使用する場合は明示的に定数に代入する必要があります。
通常のクラス定義:
class NormalClass
end
puts "NormalClass は #{defined?(NormalClass)}"
#=> NormalClass は constant
Class.new
を使用したクラス定義:
# 定数に代入する場合
DynamicClass = Class.new
puts "DynamicClass は #{defined?(DynamicClass)}"
#=> DynamicClass は constant
# 定数に代入しない場合(無名クラスとして扱われる)
unnamed_class = Class.new
puts "unnamed_class は #{unnamed_class.name}"
#=> unnamed_class は
3. 動的なクラス定義
通常のクラス定義ではクラスの内容を動的に変更したり、似たような構造のクラスをたくさん生成したりすることは難しいです。
一方でClass.new
を使えば動的なクラス定義が可能です。
User
とProduct
という2つのモデルクラスを例に通常のクラス定義とClass.new
を比較します。
通常のクラス定義(User
とProduct
を個別に定義する必要がある):
class User
attr_accessor :name, :email
def initialize(name, email)
@name = name
@email = email
end
def table_name
'users'
end
end
class Product
attr_accessor :title, :price
def initialize(title, price)
@title = title
@price = price
end
def table_name
'products'
end
end
# 使用例
user = User.new("鈴木太郎", "taro@example.com")
puts "User: #{user.name}, #{user.email}, Table: #{user.table_name}"
#=> User: 鈴木太郎, taro@example.com, Table: users
product = Product.new("メタプログラミングRuby", 3000)
puts "Product: #{product.title}, #{product.price}円, Table: #{product.table_name}"
#=> Product: メタプログラミングRuby, 3000円, Table: products
Class.new
を使用したクラス定義(User
とProduct
を動的に生成できる):
# `Class.new`で動的にクラスを生成するメソッド
def generate_model(table_name, columns)
Class.new do
columns.each do |column|
define_method(column) do
instance_variable_get("@#{column}")
end
define_method("#{column}=") do |value|
instance_variable_set("@#{column}", value)
end
end
define_method(:table_name) { table_name }
end
end
# モデルクラスの生成
User = generate_model('users', ['name', 'email'])
Product = generate_model('products', ['title', 'price'])
# Userクラスの使用例
user = User.new
user.name = "鈴木太郎"
user.email = "taro@example.com"
puts "User: #{user.name}, #{user.email}, テーブル: #{user.table_name}"
#=> User: 鈴木太郎, taro@example.com, テーブル: users
# Productクラスの使用例
product = Product.new
product.title = "メタプログラミングRuby"
product.price = 3300
puts "Product: #{product.title}, #{product.price}円, テーブル: #{product.table_name}"
#=> Product: メタプログラミングRuby, 3300円, テーブル: products
おわりに
Class.new
の使用はRubyにおけるメタプログラミングの一例です。
可読性・保守性を考慮すると実務での使用はあまり推奨されません。
一方でOSSなど高い汎用性と拡張性が求められる場面ではよく使われています。
そのためClass.new
によるクラスの定義方法を知っておくとコードリーディングにおいては役に立つ時があると思いました。
参考資料