[ruby] Ruby의 dup과 clone 메소드의 차이점은 무엇입니까?

에 대한 루비 문서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

두 방법의 차이점은 무엇입니까?



답변

서브 클래스는 다른 의미를 제공하기 위해이 메소드를 대체 할 수 있습니다. 그 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 구현은
꽤 명확하고 공정하게 준수 루비 구현하기 때문에, 종종 이러한 질문에 대한 답변을 내 소스입니다.


답변

ActiveRecord를 다룰 때도 큰 차이가 있습니다.

dup ID를 설정하지 않고 새 객체를 생성하므로 타격을 통해 데이터베이스에 새 객체를 저장할 수 있습니다 .save

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

clone 동일한 ID를 가진 새로운 객체를 생성하므로, 그 새로운 객체에 대한 모든 변경 사항은 타격시 원래 레코드를 덮어 씁니다. .save

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


답변

한 가지 차이점은 고정 된 개체와 다릅니다. clone(a 반면 고정 된 오브젝트들은 동결 된 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!


답변

둘 다 거의 동일하지만 복제는 dup보다 하나 더 많은 일을합니다. 복제시 개체의 고정 상태도 복사됩니다. Dup에서는 항상 해동됩니다.

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


답변

새로운 문서가 좋은 예를 포함한다 :

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>


답변

clone을 사용하여 Ruby에서 프로토 타입 기반 프로그래밍을 수행 할 수 있습니다. Ruby의 Object 클래스는 clone 메소드와 dup 메소드를 모두 정의합니다. clone과 dup은 모두 복사중인 객체의 얕은 복사본을 생성합니다. 즉, 객체의 인스턴스 변수는 복사되지만 참조하는 객체는 복사되지 않습니다. 예를 보여 드리겠습니다.

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"

위의 예에서 주황색 클론은 사과 객체의 상태 (즉, 인스턴스 변수)를 복사하지만 사과 객체가 다른 객체 (예 : 문자열 객체 색상)를 참조하는 경우 해당 참조는 복사되지 않습니다. 대신 사과와 오렌지는 모두 같은 물체를 참조합니다! 이 예에서 참조는 문자열 객체 ‘red’입니다. orange가 append 메소드 인 <<를 사용하여 기존 String 객체를 수정하면 문자열 객체가 ‘red orange’로 변경됩니다. 결과적으로 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'

위의 예에서 주황색 클론의 색상 인스턴스 메소드에 새로운 객체를 할당하면 더 이상 apple과 동일한 객체를 참조하지 않습니다. 따라서 사과의 색상 방법에 영향을주지 않고 주황색의 색상 방법을 수정할 수 있지만, 사과에서 다른 객체를 복제하면 해당 객체가 복사 된 인스턴스 변수에서 사과와 동일한 객체를 참조하게됩니다.

dup은 또한 복사중인 객체의 얕은 복사본을 생성하며, dup에 대해 위에서 설명한 것과 동일한 데모를 수행하면 정확히 동일한 방식으로 작동하는 것을 볼 수 있습니다. 그러나 clone과 dup에는 두 가지 주요 차이점이 있습니다. 첫째, 다른 사람들이 언급했듯이 클론은 고정 상태를 복사하고 복제는하지 않습니다. 이것은 무엇을 의미 하는가? 루비에서 ‘동결’이라는 용어는 불변의 난해한 용어로, 그 자체가 컴퓨터 과학에서 명명 된 용어이므로 무언가를 변경할 수 없습니다. 따라서 Ruby에서 고정 된 객체는 어떤 식 으로든 수정할 수 없습니다. 사실상 불변이다. 고정 된 객체를 수정하려고하면 Ruby는 RuntimeError 예외를 발생시킵니다. clone은 고정 상태를 복사하므로 복제 된 개체를 수정하려고하면 RuntimeError 예외가 발생합니다. 반대로 dup은 고정 상태를 복사하지 않기 때문에

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 

보다시피, fruit 객체 인스턴스의 싱글 톤 클래스는 클론으로 복사됩니다. 따라서 복제 된 객체는 singleton 방법 : seeded?에 액세스 할 수 있습니다. 그러나 이것은 dup의 경우가 아닙니다.

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

이제 프로토 타입 기반 프로그래밍에는 다른 클래스를 확장 한 클래스가 없으며, 청사진 역할을하는 상위 클래스에서 메소드가 파생 된 클래스의 인스턴스를 작성합니다. 대신 기본 객체가 있고 메소드와 상태를 복사하여 객체에서 새 객체를 만듭니다 (물론 복제를 통해 얕은 복사를 수행하기 때문에 인스턴스 변수 참조가 JavaScript와 마찬가지로 공유됩니다) 프로토 타입). 그런 다음 복제 된 메소드의 세부 사항을 채워서 오브젝트의 상태를 채우거나 변경할 수 있습니다. 아래 예제에는 기본 과일 오브젝트가 있습니다. 모든 과일에는 씨앗이 있으므로 number_of_seeds 메서드를 만듭니다. 그러나 사과에는 씨앗이 하나 있으므로 복제본을 만들고 세부 사항을 채 웁니다. 이제 애플을 복제 할 때 메소드를 복제 할뿐만 아니라 상태를 복제했습니다! clone은 상태의 얕은 사본 (인스턴스 변수)을 수행합니다. 그 때문에 red_apple을 얻기 위해 apple을 복제하면 red_apple은 자동으로 1 개의 seed를 갖습니다! red_apple은 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 

물론 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 

궁극적으로 clone을 사용하면 JavaScript 프로토 타입 동작과 유사한 것을 얻을 수 있습니다.


답변