2014年12月3日 星期三

Ruby, dup跟clone的不同, 淺層複製與深層複製

Ruby, dup跟clone的不同, 淺層複製與深層複製

Ruby裡面,對於物件的複製有兩個方法,分別是對物件呼叫dupclone,這兩種有什麼不同呢?這兩種方法並非只是alias的名稱而已。

先不討論淺層複製深層複製的問題,先來看看呼叫dup做了什麼事情。假設有個class:

class Foo
    def foo
        'foo'
    end
end

我們來試試看,產生Foo物件,然後對他呼叫dup

foo = Foo.new
dup_foo = foo.dup

dup_foo.foo # => 'foo'

一切依照預期,順利的進行複製了,接下來我們對foo動態增加一些Method上去,看看能不能夠複製(至於如何對物件動態增加Method請參考這篇)

class << foo
    def run
        'run'
    end
end

foo.foo # => 'foo' 原本class就有宣告 沒問題
foo.run # => 'run' 動態新增的Method 也沒問題

dup_foo_2 = foo.dup
dup_foo_2.foo # => 'foo' 原本class就有宣告 沒問題
dup_foo_2.run # => 發生錯誤,No Method Error

由此,可以知道,dup方法,僅對完整宣告的class進行複製,動態增加的不會複製,那麼我們再多測試一點,使用另一種方法增加Method。我們把整個程式碼完整再寫過

class Foo
    def foo
        'foo'
    end
end

foo = Foo.new
dup_foo = foo.dup

# 使用另一種方法增加Method到Foo上面去
class Foo
    def go
        'go'
    end
end

foo.go # => 'go'
dup_foo.go # => 'go', 正常執行

由此可見,只要是宣告成所有class Foo可以使用的Methods都可以被dup所複製,只有動態為單一Foo物件增加的Methods不能被dup複製進去

那麼clone方法,相對dup方法,就是完整地複製出一模一樣的物件

class Bar
    def bar
        'bar'
    end
end

bar = Bar.new

class << bar
    def run
        'run'
    end
end

clone_bar = bar.clone
clone_bar.bar # => 'bar'
clone_bar.run # => 'run', 動態增加的方法也一併複製了

接下來,來探討一下淺層複製深層複製的問題,都只是淺層複製

ary = ['hello']
dup_ary = ary.dup
clone_ary = ary.clone

dup_ary[0][0] = 'X'
p ary # => ["Xello"], 原本的ary被改掉了

clone_ary[0][0] = 'Y'
p ary # => ["Yello"], 原本的ary也被改掉了

如果要進行深層複製,就得遞迴對陣列中每個物件,都進行複製。

在Ruby on Rails裡面,直接提供deep_dup(但沒有提供deep_clone)

如果是在傳統的Ruby,我們可以自己實作deep_dup方法,來進行深層複製,在stackoverflow上面,我找到一個不錯的寫法

# 寫得不錯,如果物件是Array, 就繼續遞迴
class Array
    def deep_dup
        map {|x| x.deep_dup}
    end
end

# 直到不是Array,就進行複製,但這邊沒考慮到Ruby的容器還有Hash, Set ... 等,這些也需要做深層複製,所以這邊若遇到Hash, Set之類的容器就變成淺層複製了
class Object
    def deep_dup
        dup
    end
end

# 這邊小心Numeric類別的物件,沒有dup方法,所以就直接回傳本身
class Numeric
    def deep_dup
        self
    end
end

這個例子只有對Array類別進行深層複製,如果你有用到Hash, Set,甚至是你自己寫的容器,自己實做的iterator pattern ... 等,就得自行再加上deep_dup方法上去。

沒有留言:

張貼留言