1
0

【Ruby】通常のクラス定義とClass.newによるクラス定義の違い

Posted at

はじめに

通常のクラス定義とは別にClass.newという書き方でもクラスを定義できることを知りました。

この記事では通常のクラス定義とClass.newでクラス定義した場合の挙動の違いについてまとめます。

バージョンはRuby 3.3です。

Class.newを用いたクラス定義のやり方

Class.newを使用してクラスを定義する方法は、通常のクラス定義とは少し異なります。

以下に、両方のやり方を比較しながら説明します。

なおこの記事では通常の方法で定義されるクラスをNormalClassClass.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. スコープ
  2. 定数の扱い
  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を使えば動的なクラス定義が可能です。

UserProductという2つのモデルクラスを例に通常のクラス定義とClass.newを比較します。

通常のクラス定義(UserProductを個別に定義する必要がある):

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を使用したクラス定義(UserProductを動的に生成できる):

# `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によるクラスの定義方法を知っておくとコードリーディングにおいては役に立つ時があると思いました。

参考資料

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0