Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RubyのStructとOpenStructの使い方を徹底解説!値オブジェクトを簡単に作る方法

Rubyには、関連する属性をまとめて保持する「値オブジェクト」を手軽に作れるStructという組み込みクラスが用意されています。本記事では、Structの基本的な使い方から注意点、そしてOpenStructとの違いまで詳しく解説します。

そもそもStructとは何か?

StructはRubyに組み込まれたクラスで、新しいクラスを動的に生成し、値オブジェクト(Value Object)を作成するために使われます。値オブジェクトとは、関連性のある複数の属性をひとまとめにして扱うためのオブジェクトです。

具体例を挙げてみましょう

2つの座標(xy)を持つPointというデータを表したい場合、このデータはさまざまな方法で表現できます。

  • 配列:[10, 20]
  • ハッシュ:{ x: 10, y: 10 }
  • オブジェクト:Point.new(10, 20)

Pointを複数扱う予定があるなら、オブジェクトとして表現するのがベストプラクティスとされることが多いです。

しかし…

たった2つの値をまとめて格納するだけのために、わざわざクラス全体を定義したくはないですよね?

そんなときこそStructの出番です。この問題をエレガントに解決してくれます。

RubyでStructを作成する方法

Structを作成するには、newメソッドを呼び出し、そのクラスのインスタンス変数となるシンボルのリストを渡します。

生成された属性には、デフォルトで読み書き両方のアクセサメソッド(getter / setter)が自動的に定義されます。

以下はその例です

Person = Struct.new(:name, :age, :gender)

これで次のようにオブジェクトを作成できます

john  = Person.new "john", 30, "M"
david = Person.new "david", 25, "M"

別の記述方法

実務や他のコードでは、次のような書き方でStructを作成しているケースを見かけることもあります。

class Person < Struct.new(:name, :age, :gender)
end

筆者としては、この書き方はおすすめしません。

ただし、どこかで見かけたときに「ああ、あれはこういう意味だったのか」と理解できるように覚えておくと良いでしょう。

Structの大きなメリット:値オブジェクトとして比較できる

Structの優れた点のひとつは、属性に基づいて直接比較できることです。

例を見てみましょう

john == john
# true

通常のRubyオブジェクトの場合、このような等価比較を行うには自分で==メソッドを定義する必要があります。

これこそが「値オブジェクト」と呼ばれる所以です。

RubyのStructの使い方

配列やハッシュではなくStructを使う最大のメリットのひとつは、構造体のメンバーにメソッド経由でアクセスできる点にあります。

たとえば次のようになります

puts john.age
# 30

puts david.gender
# "M"

これは非常に便利です。オブジェクトの配列に対して、maxselectsumなどのEnumerableメソッドを活用できるからです。

[john, david].max_by(&:age)

いいですね!

注意:Structには落とし穴もある

Structで生成したクラスには、もうひとつ知っておくべき注意点があります。

それは、コンストラクタの引数の個数をチェックしないということです。

具体的に説明します。通常のクラスであれば、引数の個数が合わない場合に次のようなエラーが発生します。

ArgumentError: wrong number of arguments (0 for 3)

しかしStructを使っている場合、足りない引数は黙ってnilになってしまいます。

Person.new("peter")

# struct Person name="peter", age=nil, gender=nil

Structを扱う際は、この挙動を常に頭に入れておきましょう。

もうひとつの奇妙な挙動

さらに、こんな現象もあります。次のコードを見てください。

Struct.new(:a).ancestors
[#<Class:0x29b1040>, Struct, Enumerable, Object, Kernel, BasicObject]

この#<Class:0x29b1040>は、Structに自動的に追加される匿名クラス(無名クラス)です。

また、祖先チェーンにEnumerableモジュールが含まれていることにも注目してください。これにより、eachmapといった便利なメソッドを呼び出せるようになっています。

Ruby 2.5以降で使えるキーワード引数

ファイルから行を読み込み、各行が1つの項目を表しているような場面を考えてみましょう。

200 /login 18:00
404 /bacon 18:03
200 /books 18:04

このようなデータは、専用のクラスを作ると格段に扱いやすくなります。

たとえばこうです

LogEntry = Struct.new(:status, :url, :time)

LogEntry.new(200, '/books', '18:04')

ただし、各引数が何を表しているのかをもっと明確にしたい場合は、キーワード引数を使いたくなりますよね。

朗報です!

Ruby 2.5から、Structオブジェクトでキーワード引数がサポートされるようになりました。

使い方はこちら

LogEntry = Struct.new(:status, :url, :time, keyword_init: true)

LogEntry.new(status: 200, url: '/books', time: '18:04')

keyword_init: trueオプションを付けるだけでOKです。これで、ファイルをパースしてLogEntryオブジェクトへ変換する処理が、より読みやすく安全なコードになります!

OpenStructの使い方

使い捨てのオブジェクトが1つだけ必要な場合は、代わりにOpenStructの利用を検討すると良いでしょう。

コード例

require 'ostruct'

cat = OpenStruct.new(color: 'black')

puts cat.class
puts cat.color

このクラスを使うには、事前にostructをrequireする必要がある点に注意してください。

警告:OpenStructは動作が遅いことで知られており、本番環境のアプリケーションでの使用は避けるべきとされています。実際、Reddit上のschneems氏のコメントや、ベンチマーク結果を掲載したブログ記事でもこの点が指摘されています。

Struct vs OpenStruct:違いを整理

StructOpenStructの違いは以下の通りです。

  • Struct:事前に定義された属性・等価比較メソッド(==)・Enumerable機能を持つ新しいクラスを生成する
  • OpenStruct:与えられた属性を持つ新しいオブジェクトを生成する

つまり、OpenStructは高機能なHashオブジェクトのようなもの一方、Structはテンプレートから新しいクラスを作るイメージです。

まとめ

本記事では、RubyのStructとOpenStructについて学びました。それぞれのクラスが持つ特性や注意点さえ把握していれば、状況に応じて上手に使い分けられるはずです。

ポイントをおさらいしましょう。

  • Structは値オブジェクトを簡単に作れる組み込みクラス
  • アクセサメソッドや==による比較、Enumerableモジュールが自動で使える
  • コンストラクタの引数チェックがない点には要注意
  • Ruby 2.5以降はkeyword_init: trueでキーワード引数が使える
  • OpenStructは柔軟だが遅いため本番環境では非推奨

それでは、実際にコードを書いて練習してみましょう 🙂


  1. Rubyのマトリックス(行列)とは?配列・Matrixクラス・Daruを使いこなす完全ガイド

    マトリックス(行列)とは、表計算ソフトのようなデータを格納・操作するための2次元(2D)配列のことです。 マトリックスが活躍する場面: チェスやオセロなど、テーブルゲームの盤面を表現する 統計処理やデータ分析を行う グラフやプロットを生成する マトリックスは非常に強力なデータ構造なので、その使い方をマスターしておくと開発の幅が大きく広がります。この記事では、Rubyでマトリックスを扱う3つの方法——配列、Matrixクラス、そしてDaru gem——を順番に解説していきます。 Rubyでマトリックスを作成する方法 最もシンプルな方法は、配列の入れ子(ネスト)を使ってマトリックスを作ることで

  2. Rubyのエイリアス(別名定義)完全ガイド:aliasキーワードとalias_methodの違い

    Rubyでは、既存のメソッドに別名(エイリアス)を付ける方法が2つあります。 alias(キーワード) alias_method(メソッド) どちらも同じ目的で使えますが、挙動が微妙に異なるため、初心者にとって混乱しやすいトピックです。 本記事では、両者の違いを詳しく掘り下げ、しっかりと理解できるように解説していきます。 aliasキーワードとは まずはaliasから見ていきましょう。aliasはRubyのキーワードの一つです(ifやdef、classなどと同じ扱いです)。 基本的な書き方は以下の通りです。 alias print_something puts print_someth