search
LoginSignup
133

More than 5 years have passed since last update.

posted at

updated at

Organization

Ruby におけるハッシュ (Hash) と構造体 (Struct) の使い分け

概要

Ruby ではハッシュがよく使われるが、実は構造体も使える。

# ハッシュ
person = {name: "Taro", age: 16}
p person[:name]

# 構造体
Person = Struct.new(:name, :age)
person = Person.new("Taro", 16)
p person.name

どう使い分けるとよいのだろうか?

参考

ポイント1: ハッシュは動的、構造体は静的

ハッシュは存在しない要素を参照したり、要素をあとから追加できます。一方、構造体は初めに定義した要素しか扱えません。

# ハッシュ
person = {name: "Taro", age: 16}
person[:tel] # => nil
person[:sex] = "man" # 要素が追加される

# 構造体
Person = Struct.new(:name, :age)
person = Person.new("Taro", 16)
person.tel # => NameError: undefined method 'tel'
person.sex = "man" # => NameError: undefined method 'sex='

この特性ゆえ、実行時まで属性名が決まらないような場合は、ハッシュの方がきれいに対応できます。

key = ARGV[0]

# ハッシュ
p some_hash[key] # => なんとかなる

# 構造体
p some_struct.key # => ムリ
p some_struct.send(key) # => なんとかなるけど気持ち悪い

ポイント2: 等価の基準が違う

構造体は、たとえ内容が全く同じでも、元になった構造体自体が違う場合、等価になりません。

Person = Struct.new(:name, :age)
person = Person.new("Taro", 16)

Robot = Struct.new(:name, :age)
robot = Robot.new("Taro", 16)

p person == robot # => false. 越えられないカベ

ポイント3: to_a の挙動が違う

ハッシュの方は key-value のペアになった配列が得られます。

# ハッシュ
person.to_a
=> [[:name, "Taro"], [:age, 16]]

# 構造体
person.to_a
=> ["taro", 16]

ポイント4: 構造体はインスタンス生成に属性名がいらない

属性名が長くなりがちなときや、インスタンスをいくつも作るときにスッキリ。

# ハッシュ
person1 = {name: "Taro", age: 16, most_favorite_movie_ever_seen: "Toy Story"}
person2 = {name: "Jiro", age: 17, most_favorite_movie_ever_seen: "Toy Story 2"}
person3 = {name: "Maro", age: 18, most_favorite_movie_ever_seen: "Toy Story 3"}

# 構造体
Person = Struct.new(:name, :age, :most_favorite_movie_ever_seen)
person1 = Person.new("Taro", 16, "Toy Story")
person2 = Person.new("Jiro", 17, "Toy Story 2")
person3 = Person.new("Maro", 18, "Toy Story 3")

結論と感想

ポイント1の違いが一番大きいでしょう。参考リンク先では、例えば「文章中に出てくる単語をキー、その出現回数をバリューにしたいとき」のような動的な処理のときはハッシュがよいだろう、と結論付けられています。私もそう思います。

個人的に感じている構造体の便利ポイントは、単純に「ドット演算子が使える」ことです。これによって、でかいオブジェクトを返してくるメソッド(例えば open など)のスタブ化が簡単にできます。

hoge.rb
require "open-uri"

class Hoge
  def call_api
    response = open "http://something-api/"
    response.read if response.status[0] == "200"
  end
end
hoge_spec.rb
it '接続成功時は "Success" を返す' do
  hoge = Hoge.new

  # read と status に応答できるオブジェクトを即席で作る
  dummy = Struct.new(:read, :status).new('{"result": "Success"}', ["200", "OK"])
  # dummy を返すように open メソッドをスタブ化
  allow(hoge).to receive(:open).and_return(dummy)

  expect(hoge.call_api).to eq '{"result": "Success"}'
end

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
What you can do with signing up
133