07/01/2019, 14:09

The Complete Guide to Create a Copy of an Object in Ruby: Part I

Tại sao chúng ta lại cần copy các Object? Nhu cầu này xuất hiện khi chúng ta muốn thay đổi hoặc di chuyển chúng mà vẫn phải đảm bảo được tính nguyên bản của các Object này. Trong bài viết này, chúng ta cùng nhau tìm hiểu các chủ đề sau: shallow copy và deep copy dup và clone trong ruby so ...

Tại sao chúng ta lại cần copy các Object? Nhu cầu này xuất hiện khi chúng ta muốn thay đổi hoặc di chuyển chúng mà vẫn phải đảm bảo được tính nguyên bản của các Object này.

Trong bài viết này, chúng ta cùng nhau tìm hiểu các chủ đề sau:

  • shallow copy và deep copy
  • dup và clone trong ruby
  • so sánh giữa dup và clone

Nguyên tắc copy một giá trị hay một đối tượng là khái niệm chính của bất kì ngôn ngữ lập trình nào

Có rất nhiều nguyên tắc để copy các đối tượng hay các giá trị đã được thực hiện theo thời gian.

Trong khuôn khổ bài viết này, chúng ta sẽ tập trung vào shallow copy và deep copy

Chúng ta sẽ cùng nhau khám phá xem, ngôn ngữ Ruby đã cung cấp những gì để có thể sử dụng các nguyên tắc này

  • Shallow copy dựa trên một thực tế rằng: nó tạo ra một bản copy thông minh của một đối tượng cho trước. Đối tượng vừa được tạo ra này chứa chính xác các giá trị của đối tượng được copy. Nếu một biến của đối tượng được sao chép là tham chiếu đến đối tượng khác, thì chỉ địa chỉ tham chiếu của đối tượng được sao chép.

  • Deep copy là một shallow copy, thêm vào đó, tất cả các đối tượng được trỏ bởi tham chiếu trong đối tượng được sao chép cũng sẽ được sao chép.Một deep copy xảy ra khi một đối tượng được copy cùng với các đối tượng mà nó refer đến

Bây giờ, chúng ta đã hiểu hơn về các nguyên tắc của shallow copy và deep copy. Bây giờ, hãy cùng nhau khám phá cách thức mà Ruby cung cấp để sao chép các đối tượng

Object#dup

Method Object#dup return một shallow copy của đối tượng đang gọi:

 obj1 = 'Hello world!'

collection1 = [obj1, 42]
collection2 = collection1.dup

obj1.object_id              # => 70092096927380
collection1.first.object_id # => 70092096927380
collection2.first.object_id # => 70092096927380

obj1.gsub!(' ', "	")

obj1              # => "Hello	world!"
collection1.first # => "Hello	world!"
collection2.first # => "Hello	world!"

Ở ví dụ trên, một shallow copy của đối tượng collection1 được lưu trữ trong đối tượng collection2 bằng việc assign collection2 bằng collection1.dup.

Chúng ta có thể nhìn thấy collection2.first chứa một tham chiếu(địa chỉ vùng nhớ) đến obj1. Vì vậy, khi obj1 thay đổi, collection2.first cũng bị tác động thay đổi này vì lúc này obj1, collection1.first và collection2.first trỏ về cùng một địa chỉ vùng nhớ.

Thêm vào đó, Object#dup cũng sẽ gọi ra method initialize_copy dành cho các đối tượng được copy mới, bạn có thể implement method này để tương tác với các đối tượng copy mới đó

require 'forwardable'

class Collection
  attr_reader :collection

  extend Forwardable

  def_delegator :@collection, :first, :obj

  def initialize(collection)
    @collection = collection
  end

  def initialize_copy(other_obj)
    puts 'initiliaze copy'
    super
  end
end

obj1 = 'Hello world!'

collection1 = Collection.new([obj1, 42])
collection2 = collection1.dup

Ta có thể thấy method initialize_copy được gọi ra khi method collection1.dup được gọi

Object#clone

Method Object#clone cũng trả về một shallow copy của đối tượng đang gọi

obj1 = 'Hello world!'

collection1 = [obj1, 42]
collection2 = collection1.clone

obj1.object_id              # => 70092096927380
collection1.first.object_id # => 70092096927380
collection2.first.object_id # => 70092096927380

obj1.gsub!(' ', "	")

obj1              # => "Hello	world!"
collection1.first # => "Hello	world!"
collection2.first # => "Hello	world!"

Ở ví dụ trên, một shallow copy của collection1 được lưu trữ trong collection2 bằng việc gán collection2 = collection1.clone.

Chúng ta có thể thấy, collection2.first là một tham chiếu đến obj1(tức collection2.first và obj1 có cùng địa chỉ ô nhớ), vì vậy khi obj1 thay đổi, lập tức collection2.first cũng thay đổi theo.

Tương tự Object#clone cũng gọi hook method initialize_copy trên đối tượng đang gọi, vậy sự khác nhau giữa 2 method này là gì?

Mặc dù gần giống nhau, #clone có 3 điều khác biệt so với #dup

Object#freeze

Với #clone, thì trạng thái frozen của đối tượng cũng được copy, còn #dup thì không

hello_world = 'hello world!'.freeze

hello_world.object_id                # => 70172035954580
hello_world.frozen?                  # => true

clone_with_frozen_state = hello_world.clone
clone_with_frozen_state.object_id    # => 70172035953860
clone_with_frozen_state.frozen?      # => true

clone_without_frozen_state = hello_world.clone(freeze: false)
clone_without_frozen_state.object_id # => 70172035953620
clone_without_frozen_state.frozen?   # => false

hello_world_dup = hello_world.dup
hello_world_dup.object_id            # => 70172035954080
hello_world_dup.frozen?              # => false

Bạn có thể tránh việc copy cả trạng thái frozen bằng việc truyền vào tham số frozen: false đến method Object#clone

Object#extend

Khi sử dụng Object#clone, bất kì modules nào mà đối tượng được extend vẫn sẽ được copy, còn với Object#dup thì không:

class Hello
end

module World
  def hello_world
    puts 'Hello world!'
  end
end

hello = Hello.new
hello.extend World

hello.hello_world       # => Hello world!
hello.clone.hello_world # => Hello world!
hello.dup.hello_world   # => NoMethodError: undefined method `hello_world' for #<Hello:0x401b3a38>

Object-level methods

Khi sử dụng Object#clone, tất cả những method object-level của đối tượng ban đầu đều sẽ được copy, còn Object#dup thì không:

class Hello end

hello = Hello.new

def hello.hello_world
  puts 'Hello world!'
end

hello.hello_world       # => Hello world!
hello.clone.hello_world # => Hello world!
hello.dup.hello_world   # => NoMethodError: undefined method `hello_world' for #<Hello:0x401b3a38>

Tham khảo:

https://medium.com/rubycademy/the-complete-guide-to-create-a-copy-of-an-object-in-ruby-part-i-91be8b9daafd

0