Sự khác biệt giữa các phương pháp nhân đôi và nhân bản của Ruby là gì?


214

Các tài liệu Ruby chodup biết:

Nói chung, clonedupcó thể có ngữ nghĩa khác nhau trong các lớp con cháu. Trong khi cloneđược sử dụng để sao chép một đối tượng, bao gồm cả trạng thái bên trong của nó, dupthường sử dụng lớp của đối tượng hậu duệ để tạo ra thể hiện mới.

Nhưng khi tôi làm một số thử nghiệm tôi thấy chúng thực sự giống nhau:

class Test
   attr_accessor :x
end

x = Test.new
x.x = 7
y = x.dup
z = x.clone
y.x => 7
z.x => 7

Vậy sự khác biệt giữa hai phương pháp là gì?


29
Tôi ước tôi biết không chỉ đơn thuần là sự khác biệt trong việc gì dupclonelàm , mà tại sao bạn lại sử dụng cái này chứ không phải cái kia.
Andrew Grimm

1
đây cũng là một liên kết tốt - coderwall.com/p/1zflyg
Arup Rakshit

Câu trả lời:


298

Các lớp con có thể ghi đè các phương thức này để cung cấp các ngữ nghĩa khác nhau. Trong Objectchính nó, có hai sự khác biệt chính.

Đầu tiên, clonesao chép lớp singleton, trong khi dupkhông.

o = Object.new
def o.foo
  42
end

o.dup.foo   # raises NoMethodError
o.clone.foo # returns 42

Thứ hai, clonebảo tồn trạng thái đông lạnh, trong khi dupkhông.

class Foo
  attr_accessor :bar
end
o = Foo.new
o.freeze

o.dup.bar = 10   # succeeds
o.clone.bar = 10 # raises RuntimeError

Việc triển khai Rubinius cho các phương thức này thường là nguồn của tôi để trả lời cho những câu hỏi này, vì nó khá rõ ràng và việc triển khai Ruby khá tuân thủ.


15
Trong trường hợp bất cứ ai cố gắng thay đổi điều này một lần nữa: "lớp singleton", một thuật ngữ được xác định rõ trong Ruby, không chỉ bao gồm các phương thức singleton , mà còn bất kỳ hằng số nào được định nghĩa trên lớp singleton. Hãy xem xét : o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Jeremy Roman

3
câu trả lời tuyệt vời, tiếp theo là một bình luận tuyệt vời, nhưng nó đã dẫn tôi đến một cuộc rượt đuổi ngông cuồng để hiểu cú pháp đó. điều này sẽ giúp bất cứ ai khác ngoài đó cũng có thể bị nhầm lẫn: devalot.com/articles/2008/09/ruby-singleton
davidpm4 15/03/2016

1
Tôi nghĩ rằng điều đáng nói là "lớp đơn" cũng bao gồm bất kỳ mô-đun nào đã được chỉnh sửa extendtrên đối tượng ban đầu. Vì vậy, Object.new.extend(Enumerable).dup.is_a?(Enumerable)trả lại sai.
Daniel

Mặc dù câu trả lời này không trả lời câu hỏi và nêu sự khác biệt. Cũng đáng lưu ý rằng cả hai phương thức đều có nghĩa cho các tình huống khác nhau như được nêu trong tài liệu song công Object # . Ca sử dụng cho bản sao đang nhân bản một đối tượng với ý định sử dụng nó như một thể hiện đó (trong khi có một id đối tượng khác), trong khi dup được dự định sao chép một đối tượng làm cơ sở cho một thể hiện mới.
3limin4t0r

189

Khi giao dịch với ActiveRecord cũng có một sự khác biệt đáng kể:

dup tạo một đối tượng mới mà không đặt id của nó, vì vậy bạn có thể lưu một đối tượng mới vào cơ sở dữ liệu bằng cách nhấn .save

category2 = category.dup
#=> #<Category id: nil, name: "Favorites"> 

clone tạo một đối tượng mới có cùng id, vì vậy tất cả các thay đổi được thực hiện cho đối tượng mới đó sẽ ghi đè lên bản ghi gốc nếu nhấn .save

category2 = category.clone
#=> #<Category id: 1, name: "Favorites">

43
Câu trả lời NÀY là câu trả lời có IMO thông tin thực tế quan trọng nhất ... các câu trả lời khác tập trung vào bí truyền, trong khi câu trả lời này xác định một sự khác biệt thực tế quan trọng.
jpw

37
Trên đây là cụ thể cho ActiveRecord; sự khác biệt là tinh tế hơn nhiều trong Ruby tiêu chuẩn.
ahmacleod

1
@Stefan và @jvalanen: Khi tôi đang áp dụng dupclonephương pháp trên ActiveRecordđối tượng của mình , tôi nhận được kết quả ngược lại với những gì bạn đã đề cập trong câu trả lời. có nghĩa là khi tôi đang sử dụng dup, nó tạo ra một đối tượng mới với nó idđược thiết lập và trong khi sử dụng clonenó sẽ tạo ra một đối tượng mà không idđược thiết lập. bạn có thể vui lòng nhìn vào nó một lần nữa và làm rõ? . Thnx
huzefa biyawarwala

Không có gì thay đổi trong Rails 5 cả: api.rubyonrails.org/groupes/ActiveRecord/ . Vì vậy, tôi tin rằng có điều gì đó đặc biệt trong trường hợp của bạn ...
jvalanen

Tuy nhiên, cloneing một bản ghi mới chưa bao giờ được lưu nên khá an toàn thì sao? Tôi có thể xây dựng một "đối tượng mẫu" theo cách này và sao chép nó để lưu các trường hợp cụ thể không?
Cyril Duchon-Doris

30

Một sự khác biệt là với các đối tượng đông lạnh. Các clonecủa một đối tượng đông lạnh cũng được đông lạnh (trong khi một dupcủa một đối tượng đông lạnh không phải là).

class Test
  attr_accessor :x
end
x = Test.new
x.x = 7
x.freeze
y = x.dup
z = x.clone
y.x = 5 => 5
z.x = 5 => TypeError: can't modify frozen object

Một sự khác biệt khác là với các phương pháp singleton. Câu chuyện tương tự ở đây, dupkhông sao chép những điều đó, nhưng clonekhông.

def x.cool_method
  puts "Goodbye Space!"
end
y = x.dup
z = x.clone
y.cool_method => NoMethodError: undefined method `cool_method'
z.cool_method => Goodbye Space!

Điều này rất hữu ích với tôi. Nếu bạn đang tạo một giá trị không đổi bị đóng băng và chuyển nó sang một thứ như thế này: github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (Xử lý cookie Rails) thì bạn có thể dễ dàng gặp lỗi khi họ không biết đến bạn, họ nhân bản nó và sau đó cố gắng sửa đổi bản sao. lừa đảo giá trị đóng băng của bạn và chuyển qua đó cho phép bạn ít nhất đảm bảo rằng không ai vô tình sửa đổi hằng số của bạn, mà không phá vỡ Rack ở đây.
XP84

4

Cả hai đều gần giống nhau nhưng bản sao làm một điều nhiều hơn so với dup. Trong bản sao, trạng thái đóng băng của đối tượng cũng được sao chép. Trong bản dupe, nó sẽ luôn tan băng.

 f = 'Frozen'.freeze
  => "Frozen"
 f.frozen?
  => true 
 f.clone.frozen?
  => true
 f.dup.frozen?
  => false 

4

Tài liệu mới hơn bao gồm một ví dụ hay:

class Klass
  attr_accessor :str
end

module Foo
  def foo; 'foo'; end
end

s1 = Klass.new #=> #<Klass:0x401b3a38>
s1.extend(Foo) #=> #<Klass:0x401b3a38>
s1.foo #=> "foo"

s2 = s1.clone #=> #<Klass:0x401b3a38>
s2.foo #=> "foo"

s3 = s1.dup #=> #<Klass:0x401b3a38>
s3.foo #=> NoMethodError: undefined method `foo' for #<Klass:0x401b3a38>

0

Bạn có thể sử dụng bản sao để lập trình dựa trên nguyên mẫu trong Ruby. Lớp Object của Ruby định nghĩa cả phương thức clone và phương thức dup. Cả clone và dup đều tạo ra một bản sao nông của đối tượng mà nó đang sao chép; nghĩa là, các biến đối tượng của đối tượng được sao chép nhưng không phải là các đối tượng mà chúng tham chiếu. Tôi sẽ chứng minh một ví dụ:

class Apple
  attr_accessor :color
  def initialize
    @color = 'red'
  end
end

apple = Apple.new
apple.color
 => "red"
orange = apple.clone
orange.color 
 => "red"
orange.color << ' orange'
 => "red orange" 
apple.color
 => "red orange"

Lưu ý trong ví dụ trên, bản sao màu cam sao chép trạng thái (nghĩa là các biến thể hiện) của đối tượng apple, nhưng trong đó đối tượng apple tham chiếu các đối tượng khác (như màu đối tượng String), các tham chiếu đó không được sao chép. Thay vào đó, táo và cam đều tham chiếu cùng một đối tượng! Trong ví dụ của chúng tôi, tham chiếu là đối tượng chuỗi 'đỏ'. Khi màu cam sử dụng phương thức chắp thêm, <<, để sửa đổi đối tượng Chuỗi hiện có, nó sẽ thay đổi đối tượng chuỗi thành 'màu đỏ cam'. Điều này cũng có hiệu lực thay đổi apple.color, vì cả hai đều trỏ đến cùng một đối tượng String.

Là một lưu ý phụ, toán tử gán, =, sẽ gán một đối tượng mới và do đó hủy một tham chiếu. Đây là một minh chứng:

class Apple
  attr_accessor :color
  def initialize
    @color = 'red'
  end
end

apple = Apple.new
apple.color
=> "red"
orange = apple.clone
orange.color
=> "red"
orange.color = 'orange'
orange.color
=> 'orange'
apple.color
=> 'red'

Trong ví dụ trên, khi chúng ta đã gán một đối tượng mới cho phương thức thể hiện màu của bản sao màu cam, nó không còn tham chiếu cùng một đối tượng như apple. Do đó, bây giờ chúng ta có thể sửa đổi phương thức màu của màu cam mà không ảnh hưởng đến phương thức màu của quả táo, nhưng nếu chúng ta sao chép một đối tượng khác từ quả táo, thì đối tượng mới đó sẽ tham chiếu cùng một đối tượng trong các biến đối tượng được sao chép là táo.

dup cũng sẽ tạo ra một bản sao nông của đối tượng mà nó đang sao chép và nếu bạn thực hiện cùng một minh họa được hiển thị ở trên để dup, bạn sẽ thấy nó hoạt động chính xác theo cách tương tự. Nhưng có hai sự khác biệt chính giữa clone và dup. Đầu tiên, như những người khác đã đề cập, sao chép trạng thái đóng băng và dup không. Điều đó có nghĩa là gì? Thuật ngữ 'đóng băng' trong Ruby là một thuật ngữ bí truyền cho sự bất biến, bản thân nó là một danh pháp trong khoa học máy tính, có nghĩa là một cái gì đó không thể thay đổi. Do đó, một đối tượng bị đóng băng trong Ruby không thể được sửa đổi theo bất kỳ cách nào; đó là, trong thực tế, bất biến. Nếu bạn cố gắng sửa đổi một đối tượng bị đóng băng, Ruby sẽ đưa ra một ngoại lệ RuntimeError. Vì bản sao sao chép trạng thái đóng băng, nếu bạn cố gắng sửa đổi một đối tượng nhân bản, nó sẽ đưa ra một ngoại lệ RuntimeError. Ngược lại, vì dup không sao chép trạng thái đóng băng,

class Apple
  attr_accessor :color
  def initialize
    @color = 'red'
  end
end

apple = Apple.new
apple.frozen?
 => false 
apple.freeze
apple.frozen?
 => true 
apple.color = 'crimson'
RuntimeError: can't modify frozen Apple
apple.color << ' crimson' 
 => "red crimson" # we cannot modify the state of the object, but we can certainly modify objects it is referencing!
orange = apple.dup
orange.frozen?
 => false 
orange2 = apple.clone
orange2.frozen?
 => true 
orange.color = 'orange'
 => "orange" # we can modify the orange object since we used dup, which did not copy the frozen state
orange2.color = 'orange'
RuntimeError: can't modify frozen Apple # orange2 raises an exception since the frozen state was copied via clone

Thứ hai, và, thú vị hơn, sao chép bản sao lớp đơn (và do đó phương thức của nó)! Điều này rất hữu ích nếu bạn muốn thực hiện lập trình dựa trên nguyên mẫu trong Ruby. Đầu tiên, chúng ta hãy chỉ ra rằng các phương thức singleton được sao chép bằng bản sao và sau đó chúng ta có thể áp dụng nó trong một ví dụ về lập trình dựa trên nguyên mẫu trong Ruby.

class Fruit
  attr_accessor :origin
  def initialize
    @origin = :plant
  end
end

fruit = Fruit.new
 => #<Fruit:0x007fc9e2a49260 @origin=:plant> 
def fruit.seeded?
  true
end
2.4.1 :013 > fruit.singleton_methods
 => [:seeded?] 
apple = fruit.clone
 => #<Fruit:0x007fc9e2a19a10 @origin=:plant> 
apple.seeded?
 => true 

Như bạn có thể thấy, lớp singleton của thể hiện đối tượng trái cây được sao chép vào bản sao. Và do đó, đối tượng nhân bản có quyền truy cập vào phương thức singleton: seeded?. Nhưng đây không phải là trường hợp với dup:

apple = fruit.dup
 => #<Fruit:0x007fdafe0c6558 @origin=:plant> 
apple.seeded?
=> NoMethodError: undefined method `seeded?'

Bây giờ trong lập trình dựa trên nguyên mẫu, bạn không có các lớp mở rộng các lớp khác và sau đó tạo các thể hiện của các lớp có phương thức xuất phát từ lớp cha đóng vai trò là một kế hoạch chi tiết. Thay vào đó, bạn có một đối tượng cơ sở và sau đó bạn tạo một đối tượng mới từ đối tượng với các phương thức và trạng thái của nó được sao chép (tất nhiên, vì chúng ta đang thực hiện các bản sao nông thông qua bản sao, mọi đối tượng tham chiếu biến đối tượng sẽ được chia sẻ giống như trong JavaScript nguyên mẫu). Sau đó, bạn có thể điền hoặc thay đổi trạng thái của đối tượng bằng cách điền vào các chi tiết của các phương thức nhân bản. Trong ví dụ dưới đây, chúng ta có một đối tượng trái cây cơ sở. Tất cả trái cây đều có hạt, vì vậy chúng tôi tạo ra một phương thức number_of_seệt. Nhưng táo có một hạt giống, và vì vậy chúng tôi tạo một bản sao và điền vào các chi tiết. Bây giờ khi chúng tôi nhân bản táo, chúng tôi không chỉ nhân bản các phương thức mà chúng tôi còn nhân bản tiểu bang! Ghi nhớ bản sao làm một bản sao nông của trạng thái (biến thể hiện). Và vì điều đó, khi chúng tôi nhân bản táo để lấy red_apple, red_apple sẽ tự động có 1 hạt giống! Bạn có thể nghĩ red_apple là một đối tượng kế thừa từ Apple, từ đó thừa hưởng từ Fruit. Do đó, đó là lý do tại sao tôi viết hoa trái cây và táo. Chúng tôi đã đi xa với sự phân biệt giữa các lớp và các đối tượng lịch sự của bản sao.

Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
  @number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
  @number_of_seeds
end
 Apple = Fruit.clone
 => #<Object:0x007fb1d78165d8> 
Apple.number_of_seeds = 1
Apple.number_of_seeds
=> 1
red_apple = Apple.clone
 => #<Object:0x007fb1d892ac20 @number_of_seeds=1> 
red_apple.number_of_seeds
 => 1 

Tất nhiên, chúng ta có thể có một phương thức constructor trong lập trình dựa trên protoype:

Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
  @number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
  @number_of_seeds
end
def Fruit.init(number_of_seeds)
  fruit_clone = clone
  fruit_clone.number_of_seeds = number_of_seeds
  fruit_clone
end
Apple = Fruit.init(1)
 => #<Object:0x007fcd2a137f78 @number_of_seeds=1> 
red_apple = Apple.clone
 => #<Object:0x007fcd2a1271c8 @number_of_seeds=1> 
red_apple.number_of_seeds
 => 1 

Cuối cùng, bằng cách sử dụng bản sao, bạn có thể nhận được một cái gì đó tương tự như hành vi nguyên mẫu JavaScript.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.