Ruby JSONのシリアライズとデシリアライズ

概要

ゲームの状態をシリアル化および逆シリアル化するために、この 2 つの関数があります。無事に動作しました。しかし、それは非常に乱雑で乱雑に感じます。これを行うためのより良いアプローチはありますか?

2 つの異なるクラス変数があり、1 つは Main クラスから、3 つはguess クラスの親である Game クラスからシリアル化する必要があります。以下のメソッドは Main クラスから呼び出されます。

  def save_game(obj)
    saved_data = {
      score: obj.score,
      secret_word: obj.new_guess.secret_word,
      running_guess: obj.new_guess.running_guess,
      not_in_word: obj.new_guess.not_in_word
    }.to_json
    save_file(saved_data)
  end

  def load_game(obj)
    saved_data = JSON.load_file('saved/saved_game.json')
    obj.score = saved_data['score']
    obj.new_guess.secret_word, = saved_data['secret_word']
    obj.new_guess.running_guess = saved_data['running_guess']
    obj.new_guess.not_in_word = saved_data['not_in_word']
    display_secret_hidden(obj.new_guess.running_guess)
    display_not_included_letters(obj.new_guess.not_in_word)
    play_rounds
  end

私はこれを理解しようとしていますが、別の解決策を見つけることができますが、これまでのところ別の方法でこれを行うことができません。

解決策

それが Ruby の新しい Data Object の 1 つであれ、お母さんが昔作ったような昔ながらの Struct であれ、各要素をさまざまな場所でオンザフライでシリアル化しようとするよりも、ある種のデータ構造のインスタンス変数を渡す方が良いでしょう。

JSON、YAML、または Marshal のようなバイナリ形式にシリアル化する場合でも、コードをよりクリーンにする方法があります。 Marshal と YAML はオブジェクトのシリアル化をより適切に実行します。 JSON は、明示的な手順を踏まない限り、その点でもう少し制限があります。そこで、Marshal を使用して、serializd オブジェクトを簡単に復活させる方法をデモンストレーションします。

次の例では、@file@save_data のアクセス可能な属性を使用して、ゲームの保存を Games クラスにラップします。次のことを考慮してください。

class Game
  attr_accessor :file, :save_data
  SaveData =
    Struct.new *%i[score secret_word running_guess not_in_word]

  def initialize(filename="game_save.bin", **kwargs)
    @file = filename
    @save_data = SaveData.new
    @save_data.members.map { @save_data[_1] = kwargs[_1] }
  end

  def save
    File.open(@file, ?w) { Marshal.dump @save_data, _1 }
  end

  def load
    @save_data = Marshal.load File.read(file)
  end
end

if __FILE__ == $0
  # create a new game and populate some data.
  game = Game.new score: 1, foo: 2, not_in_word: 3

  # save the game to a file, then clobber the file
  # and display the now-empty Struct.
  game.save
  game.save_data = Game::SaveData.new
  game.save_data

  # load and display the previous save
  game.load
end

Struct を使用すると、受け渡すことができるデータ オブジェクトが得られるため、この目的には最適です。 Struct オブジェクトとグロブ化されたキーワード引数の分解を組み合わせることで、何も失うことなくコードが大幅に簡素化され、ドライアップされます。