ความแตกต่างระหว่างวิธีการซ้ำและการรูบี้ของรูบี้คืออะไร?


214

เอกสารทับทิมสำหรับdupพูด:

โดยทั่วไปcloneและdupอาจมีความหมายที่แตกต่างกันในชั้นเรียนสืบทอด ในขณะที่cloneใช้ในการทำซ้ำวัตถุรวมถึงสถานะภายในของมันdupมักจะใช้ชั้นเรียนของวัตถุสืบทอดมาเพื่อสร้างอินสแตนซ์ใหม่

แต่เมื่อฉันทำการทดสอบฉันพบว่าพวกเขาเหมือนกันจริง:

class Test
   attr_accessor :x
end

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

ดังนั้นความแตกต่างระหว่างสองวิธีคืออะไร?


29
ฉันหวังว่าฉันจะรู้ไม่เพียง แต่ความแตกต่างในสิ่งที่ dupและcloneทำ แต่ทำไมคุณต้องใช้อย่างใดอย่างหนึ่งมากกว่าที่อื่น ๆ
Andrew Grimm

1
นี่คือลิงค์ที่ดีเช่นกัน - coderwall.com/p/1zflyg
Arup Rakshit

คำตอบ:


298

คลาสย่อยอาจแทนที่วิธีการเหล่านี้เพื่อให้ความหมายที่แตกต่างกัน ในObjectตัวมันเองมีความแตกต่างที่สำคัญสองประการ

ขั้นแรกcloneคัดลอกคลาสซิงเกิลในขณะที่dupไม่

o = Object.new
def o.foo
  42
end

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

ประการที่สองcloneรักษาสถานะแช่แข็งในขณะที่dupไม่

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

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

การใช้ Rubinius สำหรับวิธีการเหล่านี้ มักเป็นแหล่งที่มาของคำตอบสำหรับคำถามเหล่านี้เนื่องจากค่อนข้างชัดเจนและการใช้งาน Ruby ที่เป็นไปตามมาตรฐาน


15
ในกรณีที่ทุกคนพยายามที่จะเปลี่ยนสิ่งนี้อีกครั้ง: "singleton class" ซึ่งเป็นคำที่กำหนดไว้อย่างดีใน Ruby รวมถึงไม่เพียง แต่วิธีการเดียว แต่ยังคงค่าคงที่ใด ๆ ที่กำหนดไว้ในชั้นเดียว พิจารณา: o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Jeremy Roman

3
คำตอบที่ดีตามด้วยความคิดเห็นที่ดี แต่มันทำให้ฉันในการไล่ล่าห่านป่าที่จะเข้าใจไวยากรณ์ที่ สิ่งนี้จะช่วยให้คนอื่น ๆ ที่นั่นอาจจะสับสน: devalot.com/articles/2008/09/ruby-singleton
davidpm4

1
ฉันคิดว่ามันคุ้มค่าที่จะกล่าวถึงว่า "คลาสซิงเกิลตัน" รวมถึงโมดูลใด ๆ ที่ถูกextendแก้ไขบนวัตถุดั้งเดิม ดังนั้นObject.new.extend(Enumerable).dup.is_a?(Enumerable)ผลตอบแทนที่ผิดพลาด
Daniel

แม้ว่าคำตอบนี้จะตอบคำถามและระบุความแตกต่าง เป็นที่น่าสังเกตว่าทั้งสองวิธีมีไว้สำหรับสถานการณ์ที่แตกต่างกันตามที่ระบุไว้ในเอกสารObject # dup กรณีการใช้งานสำหรับโคลนเป็นโคลนวัตถุที่มีความตั้งใจที่จะใช้มันเป็นเช่นเดียวกันกับที่ (ในขณะที่มี id ของวัตถุที่แตกต่างกัน) ในขณะที่dupมีจุดมุ่งหมายที่จะซ้ำกันวัตถุที่เป็นฐานสำหรับอินสแตนซ์ใหม่
3limin4t0r

189

เมื่อจัดการกับ ActiveRecord ก็มีความแตกต่างที่สำคัญเช่นกัน:

dup สร้างวัตถุใหม่โดยไม่ต้องตั้งค่า ID ดังนั้นคุณสามารถบันทึกวัตถุใหม่ไปยังฐานข้อมูลโดยการกดปุ่ม .save

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

clone สร้างวัตถุใหม่ด้วยรหัสเดียวกันดังนั้นการเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นกับวัตถุใหม่นั้นจะเขียนทับบันทึกต้นฉบับถ้ากดปุ่ม .save

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

43
คำตอบนี้เป็นคำตอบที่มีข้อมูลการปฏิบัติที่สำคัญที่สุดของ IMO ... คำตอบอื่น ๆ อาศัยอยู่กับความลับขณะที่คำตอบนี้ระบุถึงความแตกต่างในทางปฏิบัติที่สำคัญ
jpw

37
ด้านบนเป็นแบบเฉพาะสำหรับ ActiveRecord ความแตกต่างที่ลึกซึ้งยิ่งขึ้นในทับทิมมาตรฐาน
ahmacleod

1
@Stefan และ @jvalanen: เมื่อฉันใช้dupและcloneวิธีการกับActiveRecordวัตถุของฉันฉันได้รับผลลัพธ์ย้อนกลับของสิ่งที่คุณได้กล่าวถึงในคำตอบ ซึ่งหมายความว่าเมื่อฉันใช้dupมันจะสร้างวัตถุใหม่ที่มีidการตั้งค่าและในขณะที่ใช้cloneมันสร้างวัตถุโดยไม่ได้มีidการตั้งค่า คุณช่วยลองดูอีกครั้งและอธิบายได้ไหม? . ขอบคุณ
huzefa biyawarwala

ไม่มีอะไรที่มีการเปลี่ยนแปลงในทางรถไฟ 5 อย่างใดอย่างหนึ่ง: api.rubyonrails.org/classes/ActiveRecord/... ดังนั้นผมเชื่อว่ามีบางสิ่งที่พิเศษในกรณีของคุณ ...
jvalanen

อย่างไรก็ตามcloneการสร้างสถิติใหม่ที่ไม่เคยได้รับการบันทึกควรจะปลอดภัยหรือไม่ ฉันสามารถสร้าง "เท็มเพลตวัตถุ" ด้วยวิธีนี้และโคลนมันเพื่อบันทึกอินสแตนซ์ที่เฉพาะเจาะจงได้หรือไม่?
Cyril Duchon-Doris

30

สิ่งหนึ่งที่แตกต่างคือกับวัตถุแช่แข็ง cloneของวัตถุแช่แข็งยังถูกแช่แข็ง (ในขณะที่dupของวัตถุแช่แข็งไม่ได้)

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

ความแตกต่างก็คือด้วยวิธีการเดี่ยว เรื่องเดียวกันที่นี่dupไม่ได้คัดลอกสิ่งเหล่านั้น แต่cloneทำ

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!

มันมีประโยชน์มากสำหรับฉัน หากคุณกำลังสร้างค่าคงที่แช่แข็งและส่งผ่านสิ่งนี้: github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (การจัดการคุกกี้ของ Rails) คุณจะได้รับข้อผิดพลาดได้อย่างง่ายดาย เมื่อพวกเขาไม่รู้จักคุณพวกเขาโคลนมันแล้วพยายามปรับเปลี่ยนโคลน การทำซ้ำมูลค่าการแช่แข็งของคุณและการส่งผ่านที่ช่วยให้คุณอย่างน้อยรับประกันว่าไม่มีใครแก้ไขค่าคงที่ของคุณโดยไม่ตั้งใจโดยไม่ทำลาย Rack ที่นี่
XP84

4

ทั้งสองเหมือนกันเกือบ แต่โคลนทำสิ่งเดียวมากกว่าดูเพล็ก ในโคลนสถานะการแช่แข็งของวัตถุจะถูกคัดลอกด้วย ในซ้ำมันจะถูกละลาย

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

4

doc ใหม่รวมถึงการเป็นตัวอย่างที่ดี:

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

คุณสามารถใช้โคลนเพื่อทำการเขียนโปรแกรมต้นแบบใน Ruby คลาส Object ของรูบี้กำหนดทั้งวิธีการโคลนและวิธีการซ้ำ ทั้งโคลนและสำเนาสร้างสำเนาวัตถุที่คัดลอกมาตื้น ๆ ; นั่นคือตัวแปรอินสแตนซ์ของวัตถุจะถูกคัดลอก แต่ไม่ใช่วัตถุที่พวกเขาอ้างอิง ฉันจะแสดงตัวอย่าง:

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"

โปรดสังเกตในตัวอย่างข้างต้นโคลนสีส้มคัดลอกสถานะ (นั่นคือตัวแปรอินสแตนซ์) ของวัตถุ Apple แต่ที่วัตถุ Apple อ้างอิงวัตถุอื่น (เช่นสีวัตถุ String) การอ้างอิงเหล่านั้นจะไม่ถูกคัดลอก ในทางกลับกันแอปเปิ้ลและสีส้มต่างอ้างถึงวัตถุเดียวกัน! ในตัวอย่างของเราการอ้างอิงคือวัตถุสตริง 'สีแดง' เมื่อสีส้มใช้วิธีการผนวก, <<, เพื่อปรับเปลี่ยนวัตถุสตริงที่มีอยู่มันเปลี่ยนวัตถุสตริงเป็น 'สีส้มสีแดง' สิ่งนี้มีผลในการเปลี่ยนแปลง apple.color เช่นกันเนื่องจากทั้งคู่ชี้ไปที่วัตถุ String เดียวกัน

ในฐานะที่เป็นบันทึกด้านข้างผู้ประกอบการที่ได้รับมอบหมาย = จะกำหนดวัตถุใหม่และทำลายการอ้างอิง นี่คือการสาธิต:

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'

ในตัวอย่างข้างต้นเมื่อเรากำหนดวัตถุใหม่ที่สดใหม่ให้กับวิธีการอินสแตนซ์สีของโคลนสีส้มมันไม่ได้อ้างอิงวัตถุเดียวกันกับแอปเปิ้ลอีกต่อไป ดังนั้นตอนนี้เราสามารถปรับเปลี่ยนวิธีสีของส้มโดยไม่ส่งผลกระทบต่อวิธีการสีของแอปเปิ้ล แต่ถ้าเราโคลนวัตถุอื่นจากแอปเปิ้ลวัตถุใหม่นั้นจะอ้างอิงวัตถุเดียวกันในตัวแปรอินสแตนซ์ที่คัดลอกเป็นแอปเปิ้ล

ดูปองท์จะสร้างสำเนาตื้น ๆ ของวัตถุที่กำลังคัดลอกและหากคุณทำการสาธิตแบบเดียวกันกับที่แสดงข้างบนเป็นคู่คุณจะเห็นว่ามันทำงานในลักษณะเดียวกัน แต่มีความแตกต่างที่สำคัญระหว่างโคลนและสอง ครั้งแรกตามที่คนอื่นพูดถึงการคัดลอกโคลนสถานะแช่แข็งและสองไม่ได้ สิ่งนี้หมายความว่า? คำว่า 'แช่แข็ง' ในรูบีเป็นคำลึกลับสำหรับการเปลี่ยนรูปแบบซึ่งตัวเองเป็นศัพท์ในวิทยาศาสตร์คอมพิวเตอร์ซึ่งหมายความว่าสิ่งที่ไม่สามารถเปลี่ยนแปลงได้ ดังนั้นวัตถุแช่แข็งใน Ruby ไม่สามารถแก้ไขได้ในทางใดทางหนึ่ง มันคือผลไม่เปลี่ยนรูป หากคุณพยายามที่จะแก้ไขวัตถุแช่แข็ง Ruby จะเพิ่มข้อยกเว้น RuntimeError เนื่องจากโคลนคัดลอกสถานะน้ำแข็งถ้าคุณพยายามปรับเปลี่ยนวัตถุโคลนมันจะยกข้อยกเว้น RuntimeError ตรงกันข้ามเนื่องจากสำเนาไม่ได้คัดลอกสถานะแช่แข็ง

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

ที่สองและที่น่าสนใจกว่าคือการลอกแบบคลาสซิงเกิล (และด้วยวิธีการของมัน)! สิ่งนี้มีประโยชน์มากหากคุณต้องการดำเนินการเขียนโปรแกรมต้นแบบใน Ruby ก่อนอื่นมาแสดงให้เห็นว่าแท้จริงแล้ววิธีการเดี่ยวจะถูกคัดลอกด้วยโคลนและจากนั้นเราสามารถนำไปใช้ในตัวอย่างของการเขียนโปรแกรมต้นแบบใน 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 

อย่างที่คุณเห็นคลาสเดี่ยวของวัตถุผลไม้ถูกคัดลอกไปยังโคลน และด้วยเหตุนี้วัตถุที่ลอกเลียนแบบมีการเข้าถึงวิธีการเดี่ยว: seeded? แต่นี่ไม่ใช่กรณีที่มีซ้ำ:

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

ในขณะนี้ในการเขียนโปรแกรมโดยใช้ต้นแบบคุณไม่มีคลาสที่ขยายคลาสอื่นแล้วสร้างอินสแตนซ์ของคลาสที่วิธีการสืบทอดมาจากคลาสพาเรนต์ที่ทำหน้าที่เป็นพิมพ์เขียว แต่คุณมีวัตถุฐานแล้วสร้างวัตถุใหม่จากวัตถุด้วยวิธีการและสถานะที่คัดลอก (แน่นอนเนื่องจากเราทำสำเนาตื้นผ่านโคลนวัตถุใด ๆ ที่การอ้างอิงตัวแปรอินสแตนซ์จะถูกแชร์ใน JavaScript ต้นแบบ) จากนั้นคุณสามารถเติมหรือเปลี่ยนสถานะของวัตถุโดยการกรอกรายละเอียดของวิธีการโคลน ในตัวอย่างด้านล่างเรามีวัตถุผลไม้ฐาน ผลไม้ทุกชนิดมีเมล็ดดังนั้นเราจึงสร้างเมธอด number_of_seeds แต่แอปเปิ้ลมีหนึ่งเมล็ดดังนั้นเราจึงสร้างโคลนและกรอกรายละเอียด ตอนนี้เมื่อเราโคลนแอปเปิ้ลเราไม่เพียง แต่โคลนวิธี แต่เราโคลนรัฐ! โปรดจำไว้ว่าโคลนทำสำเนาของรัฐที่ตื้น (ตัวแปรอินสแตนซ์) และด้วยเหตุนี้เมื่อเราโคลนแอปเปิ้ลเพื่อรับ red_apple, red_apple จะมี 1 เมล็ดโดยอัตโนมัติ! คุณสามารถนึกถึง red_apple เป็นวัตถุที่สืบทอดมาจาก Apple ซึ่งจะสืบทอดมาจาก Fruit ด้วยเหตุนี้ฉันจึงเปลี่ยนมาใช้ Fruit และ Apple เป็นตัวพิมพ์ใหญ่ เราแยกความแตกต่างระหว่างคลาสและวัตถุด้วยความอนุเคราะห์ของโคลนนิ่ง

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 

แน่นอนว่าเราสามารถมีวิธีการสร้างในการเขียนโปรแกรมแบบอิงโปรโตเปีย:

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 

ในที่สุดเมื่อใช้โคลนคุณสามารถได้รับสิ่งที่คล้ายกับพฤติกรรมต้นแบบ JavaScript

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.