dup
のRubyドキュメント と言う:
一般に、
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
それでは、2つの方法の違いは何ですか?
サブクラスはこれらのメソッドをオーバーライドして、異なるセマンティクスを提供します。 Object
自体には、2つの重要な違いがあります。
まず、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実装だからです。
ActiveRecordを扱う場合、次のような大きな違いもあります。
dup
idを設定せずに新しいオブジェクトを作成するため、.save
を押すことで新しいオブジェクトをデータベースに保存できます
category2 = category.dup
#=> #<Category id: nil, name: "Favorites">
clone
は同じIDで新しいオブジェクトを作成するため、.save
を押すと、その新しいオブジェクトに加えられたすべての変更が元のレコードを上書きします
category2 = category.clone
#=> #<Category id: 1, name: "Favorites">
違いの1つは、フリーズオブジェクトです。凍結されたオブジェクトの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
もう1つの違いは、シングルトンメソッドの場合です。ここで同じ話、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よりも1つのことを行います。クローンでは、オブジェクトの凍結状態もコピーされます。二重では、常に解凍されます。
f = 'Frozen'.freeze
=> "Frozen"
f.frozen?
=> true
f.clone.frozen?
=> true
f.dup.frozen?
=> false
newer 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>
クローンを使用して、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"
上記の例では、オレンジのクローンがAppleオブジェクトの状態(つまり、インスタンス変数)をコピーしますが、Appleオブジェクトは他のオブジェクト(Stringなど)を参照します。オブジェクトの色)、それらの参照はコピーされません。代わりに、Appleとorangeは両方とも同じオブジェクトを参照します!この例では、参照は文字列オブジェクト「red」です。 orangeがappendメソッド<<を使用して既存のStringオブジェクトを変更すると、stringオブジェクトが「red orange」に変更されます。これらは両方とも同じStringオブジェクトを指しているため、実際にはApple.colorも変更されます。
サイドノートとして、代入演算子=は、新しいオブジェクトを割り当てるため、参照を破棄します。デモは次のとおりです。
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と同じオブジェクトを参照しなくなりました。したがって、Appleのカラーメソッドに影響を与えることなくオレンジのカラーメソッドを変更できるようになりましたが、Appleから別のオブジェクトを複製すると、その新しいオブジェクトはAppleとコピーされたインスタンス変数の同じオブジェクトを参照します。
dupはコピーするオブジェクトの浅いコピーも作成します。dupに対して上記と同じデモンストレーションを実行した場合、まったく同じように機能することがわかります。ただし、クローンとdupには2つの大きな違いがあります。最初に、他の人が述べたように、クローンは凍結状態をコピーしますが、dupはコピーしません。これは何を意味するのでしょうか? Rubyの「凍結」という用語は不変の難解な用語であり、それ自体はコンピューターサイエンスの命名法であり、何かを変更することはできません。したがって、Ruby内のフリーズされたオブジェクトは変更できません。実際には、不変です。凍結したオブジェクトを変更しようとすると、RubyはRuntimeError例外を発生させます。クローンは凍結状態をコピーするため、クローンオブジェクトを変更しようとすると、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でプロトタイプベースのプログラミングを行う場合に非常に便利です。最初に、実際にシングルトンメソッドがcloneでコピーされることを示しましょう。次に、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?にアクセスできます。しかし、これはdupには当てはまりません。
Apple = fruit.dup
=> #<Fruit:0x007fdafe0c6558 @Origin=:plant>
Apple.seeded?
=> NoMethodError: undefined method `seeded?'
プロトタイプベースのプログラミングでは、他のクラスを拡張し、ブループリントとして機能する親クラスから派生したメソッドを持つクラスのインスタンスを作成するクラスはありません。代わりに、ベースオブジェクトがあり、そのメソッドと状態がコピーされたオブジェクトから新しいオブジェクトを作成します(もちろん、クローンを介して浅いコピーを行っているため、インスタンス変数が参照するオブジェクトはJavaScriptと同様に共有されます)プロトタイプ)。次に、複製されたメソッドの詳細を入力して、オブジェクトの状態を入力または変更できます。以下の例では、ベースフルーツオブジェクトがあります。すべての果物には種がありますので、メソッドnumber_of_seedsを作成します。しかし、リンゴには1つの種子があるため、クローンを作成して詳細を入力します。 Appleを複製するとき、メソッドを複製するだけでなく、状態も複製しました! cloneは状態(インスタンス変数)の浅いコピーを行うことを忘れないでください。そのため、Appleを複製してred_Appleを取得すると、red_Appleは自動的に1つのシードを取得します。 red_AppleはAppleから継承するオブジェクトと考えることができ、AppleはFruitから継承します。したがって、だからこそフルーツとアップルを大文字にしました。クローンのおかげで、クラスとオブジェクトの区別を廃止しました。
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プロトタイプの動作に似たものを取得できます。