Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Object Marshalling ใน Ruby: คู่มือปฏิบัติจริง

ในบทความนี้ เราจะดำดิ่งสู่ object marshalling เราจะอธิบายว่ามันคืออะไร ดูที่โมดูล Marshall และผ่านตัวอย่าง แล้วเราจะไปลึกกว่านั้นเปรียบเทียบเมธอด _dump และ self._load มาเริ่มกัน!

Object Marshalling คืออะไร?

เมื่อคุณเขียนโค้ด คุณอาจต้องการ save ออบเจ็กต์และ transmit ไปยังโปรแกรมอื่นหรือ reuse ในการ execute โปรแกรมครั้งต่อไป Object marshalling ถูกใช้ใน Sidekiq ตัวอย่างเช่น เมื่อ Sidekiq job ถูก enqueued ใน Ruby on Rails application การ serialize ของ job นี้ — ซึ่งไม่ใช่อะไรมากกว่าออบเจ็กต์ — จะถูก insert ลงใน Redis กระบวนการ Sidekiq จึงสามารถ deserialize JSON นี้และ reconstitute job ต้นฉบับจาก JSON

ใน computer programming กระบวนการ serialization และ deserialization ของออบเจ็กต์นี้คือสิ่งที่เราเรียกทั่วไปว่า object marshalling ตอนนี้ มาดูว่า Ruby native provides อะไรบ้างเพื่อจัดการ Object Marshalling

The Marshal Module

เนื่องจาก Ruby เป็น fully object oriented programming language มัน提供方式來 serialize และ store objects โดยใช้โมดูล Marshal ใน standard library มัน允许你将对象序列化为可以存储并在另一个 Ruby 进程中反序列化的字节流

所以 让我们序列化一个字符串并仔细看看序列化的对象

hello_world = 'hello world!'
serialized_string = Marshal.dump(hello_world)
# => "\x04\bI\"\x11hello world!\x06:\x06ET"

serialized_string.class
# => String

deserialized_hello_world = Marshal.load(serialized_string)
# => "hello world!"

hello_world.object_id
# => 70204420126020

deserialized_hello_world.object_id
# => 70204419825700

เราจึงเรียกโมดูลเมธอด Marshal.dump เพื่อ serialize string ของเรา เราเก็บค่าส่งคืน — ซึ่ง包含我們序列化的字符串 — 在變量 serialized_string 中 這個字符串可以存儲在文件中 並且該文件可以被重用以在另一個進程中重新構成原始對象 然後我們調用 Marshal.load 方法從字節流重新構成原始對象

我們可以看到這個新重新構成的字符串與 hello_world 字符串有不同的 object_id 這意味著它是一個不同的對象 但它包含相同的數據

很酷! 但 Marshal 模塊如何能夠重新構建字符串? 如果我想控制序列化和反序列化哪些屬性怎麼辦?

A Concrete Example of Object Marshalling

為了回答這些問題 讓我們在一個名為 User 的自定義 struct 上實現 marshalling strategy

User = Struct.new(:fullname, :age, :roles)
user = User.new('Mehdi Farsi', 42, [:admin, :operator])

User struct 定義了 3 個屬性: fullname, age, 和 roles 對於這個例子 我們有一條業務規則 我們只在符合以下標準時序列化:

  • fullname 包含少於 64 個字符
  • roles 陣列不包含 :admin 角色

為此 我們可以定義 User#marshal_dump 方法來實現我們自定義的序列化策略 當我們用 User struct 的實例作為參數調用 Marshal.dump 方法時 將調用此方法 讓我們定義這個方法:

User = Struct.new(:age, :fullname, :roles) do
  def marshal_dump
    {}.tap do |result|
      result[:age] = age
      result[:fullname] = fullname if fullname.size <= 64
      result[:roles] = roles unless roles.include? :admin
    end
  end
end

user = User.new(42, 'Mehdi Farsi', [:admin, :operator])
user_dump = Marshal.dump(user)
# 'in User#marshal_dump'
user_dump
# => "\x04\bU:\tUser{\a:\bageI\"\x10Mehdi Farsi\x06:\x06ET:\rfullnamei/\"

在上面的例子中 我們可以看到我們的 User#marshal_dump 方法在調用 Marshal.dump(user) 時被調用 user_dump 變量包含我們 User 實例序列化的字符串

現在我們有了 dump 讓我們 deserialize 它以重新構成我們的 user 為此 我們定義 User#marshal_load 方法負責實現 User dump 的反序列化策略

所以讓我們定義這個方法

User = Struct.new(:age, :fullname, :roles) do
  def marshal_dump
    {}.tap do |result|
      result[:age] = age
      result[:fullname] = fullname if fullname.size <= 64
      result[:roles] = roles unless roles.include? :admin
    end
  end

  def marshal_load(serialized_user)
    self.age = serialized_user[:age]
    self.fullname = serialized_user[:fullname]
    self.roles = serialized_user[:roles] || []
  end
end

user = User.new(42, 'Mehdi Farsi', [:admin, :operator])
user_dump = Marshal.dump(user)
# 'in User#marshal_dump'
user_dump
# => "\x04\bU:\tUser{\a:\bagei/:\rfullnameI\"\x10Mehdi Farsi\x06:\x06ET"
original_user = Marshal.load(user_dump)
# 'in User#marshal_load'
original_user
# => #<struct User age=42, fullname="Mehdi Farsi", roles=[]>

在上面的例子中 我們可以看到我們的 User#marshal_load method 在調用 Marshal.load(user_dump) 時被調用 original_user 變量包含一個 struct 這是我們 user 實例的重新構成

請注意 original_user.roles 不類似於 user.roles 陣列 因為在序列化期間 user.roles 包含 :admin 角色 所以 user.roles 沒有被序列化進 user_dump 變量

The _dump 和 self._load Methods

當調用 Marshal.dump 和 Marshal.load 時 這些方法調用傳遞給這些方法的參數對象上的 marshal_dump 和 marshal_load 方法

但 如果我告訴你 Marshal.dump 和 Marshal.load 方法嘗試調用傳遞給參數的對象上的另外兩個名為 _dump 和 self._load 的方法?

The _dump Method

marshal_dump 和 _dump 方法之間的區別是:

  • 使用 _dump 方法時 你需要在較低層級處理序列化策略 — 你需要返回一個表示要序列化的數據的字符串
  • 如果兩者都定義了 marshal_dump 方法優先於 _dump

讓我們看看以下例子:

User = Struct.new(:age, :fullname, :roles) do
  def _dump(level)
    [age, fullname].join(':')
  end
end

user = User.new(42, 'Mehdi Farsi', [:admin, :operator])
Marshal.dump(user)
# => "\x04\bIu:\tUser\x1342:Mehdi Farsi\x06:\x06EF"

在 User#_dump 方法中 我們必須實例化並返回序列化對象 — 代表您序列化的字符串

在以下例子中 我們定義 User#marshal_dump 和 User#_dump 方法並返回字符串以查看哪個方法被調用

User = Struct.new(:age, :fullname, :roles) do
  def marshal_dump
    'in User#marshal_dump'
  end

  def _dump(level)
    'in User#_dump'
  end
end

user = User.new(42, 'Mehdi Farsi', [:admin, :operator])
user_dump = Marshal.dump(user)
# "in User#marshal_dump"

我們可以看到即使兩者都定義了 只有 User#marshal_dump 被調用

The self._load Method

現在 讓我們看看 marshal_load 和 _load 方法

marshal_load 和 _load 方法之間的區別是:

  • 使用 _load 方法時 你需要在較低層級處理反序列化策略 — 你負責實例化原始對象
  • marshal_load 方法接受反序列化對象作為參數 而 self._load 方法接受序列化字符串作為參數
  • marshal_load 方法是實例方法 而 self._load 是類方法

讓我們看看以下例子:

User = Struct.new(:age, :fullname, :roles) do
  def _dump(level)
    [age, fullname].join(':')
  end

  def self._load(serialized_user)
    user_info = serialized_user.split(':')
    new(*user_info, Array.new)
  end
end

user = User.new(42, 'Mehdi Farsi', [:admin, :operator])
user_dump = Marshal.dump(user)
user_dump
# => "\x04\bIu:\tUser\x1342:Mehdi Farsi\x06:\x06EF"
original_user = Marshal.load(user_dump)
original_user
# => #<struct User age="Mehdi Farsi", fullname=42, roles=[]>

在 User._load 方法中:

  • 我們反序列化 User#_dump 方法返回的字符串
  • 我們通過傳遞反序列化信息實例化一個新的 User

我們可以看到我們負責分配和實例化用於重新構成原始用戶的對象

所以 Marshal.load 結合 marshal_load 負責實例化重新構成的原始對象 然後在新實例化的對象上調用 marshal_load 方法並將序列化對象作為參數傳遞

相反 地 Marshal.load 結合 _load 讓 self._load 類方法負責:

  • 反序列化 _dump 方法返回的數據
  • 實例化重新構成的原始對象

Conclusion

根據您的需求 您可以決定實現較高或較低的序列化/反序列化策略 為此 您可以使用 Marshal 模塊結合適當的 Marshal hook methods

Voilà!