因為Prototype Pattern實在太常用了,所以微軟直接幫你把它定義好,就是ICloneable,
只要實作它就代表你一定要做出clone功能,那就等於是實現了Prototype Pattern。
Prototype Pattern的目的:實現clone功能,複製出參數相同的物件,不須再對物件做設定。
先來看看實現 Prototype Pattern 的架構圖:
Resume物件內除了有自己的變數儲存資訊,
也會有WorkExperence物件來儲存資訊。
搭配程式碼看會比較了解架構。
淺複製 ( Shallow Copy )
Clone大家都再用,但有些特殊情況需要特別注意。
如果複製的是"數值型別",那都不會出現什麼問題,
完整範例可以參考 Chapter_9.1 中宣告為string的資料(PersonalInfo)被複製後的前後變化,
[其實string不完全是數值型別,而是它擁有數值型別特性的特殊參考類型,所以行為和數值型別類似,請參考余大文章。重點即是string的值被修改後,就會重新配置記憶體。]
這個範例使用的是淺複製,也就是Clone()內呼叫的是 MemberwiseClone() ,
能夠注意到內部被複製的"實質型別"(string)怎麼修改都不會發生問題。
以下提供使用者程式碼與結果做比較, b 和 c 物件都是由 a clone()而來,
請觀察紅框內的設定與結果:
SetPersonalInfo()內設定的是String型別的欄位。
可以觀察到,即使 c 物件重新設定了年齡,對於其他被複製的物件也不會有所影響,
數值型別的clone是複製值給新物件,所以各自獨立。
那麼如果想複製的是"參考型別"呢?
也就是被new出來的物件,此例為WorkExperience物件,
(特別注意!如果沒有new,該物件一樣會被生成,
但該物件內所有參考型別都會被當成實質型別來對待,也就是各自獨立。)
clone則會複製該物件的"參考",也就是說不管複製幾個出來,
新物件全都指向同一個物件,因為參考都一樣阿!
直接來看淺複製對於"使用到的物件(參考型別)"會有什麼影響,
請參考 Chapter_9.1 中對於Clone之後的三個WorkExperence物件變化。
以下提供使用者程式碼與結果做比較,請觀察黃框內的設定與結果:
SetWorkExperience()內設定的是WorkExperience型別的物件。
可以發現最後一個物件 c 設定了WorkExperience物件後,
a 和 b 的WorkExperience物件內的資訊也一併被修改成 c 的資訊了,
因為 a b c 三個所存取的WorkExperience物件都是同一個。
那麼要如何讓 a b c 三者擁有的WorkExperience物件都是各自獨立的呢?
那就要進行深複製了。
深複製 ( Deep Copy )
來看看實現 Prototype Pattern 淺複製與深複製的架構圖:
其實就差在把你想深複製的類別也實作ICloneable而以,
但一樣需要對複製出來的參考物件做初值設定,因為它並不會幫你把值也複製過去,
來看看怎麼做:
先讓WorkExperence實作ICloneable介面。
並在Resume類別內的Clone()加入
"WorkExperence物件的複製"與
"賦值給被複製的參考物件"的片段。
透過新增的建構式來複製被參考的物件。(呼叫WorkExperence內的Clone()來進行物件的複製)
接著以相同的使用者程式碼來看看這一次的改變。
使用者程式碼:
a b c 三個物件都各自設定了WorkExperence參考物件,
但結果卻是各自獨立的,達成了深複製的效果。
這樣的方式有一些缺點:
- 但總會遇到你要使用的類別不是你寫的吧?不是你寫的類別,沒辦法修改,又該如何幫它實作IClonable呢?
- 如果WorkExperence類別內的欄位有增減,那麼在使用者端的賦值片段又得去做增減,容易造成疏漏。
因此我認為大話作者的方式只能應用再比較少的情境(類別都是自己寫的),
網路搜尋其他方式,有序列化和反射兩種做法,這邊先參考 余小章 大神的文章,
利用序列化的方式進行深複製,那我就依照原本的範例來做修改吧。
(余大內使用的是BinaryFormatter,而官方文件提到 "二進位序列化可能帶來危害。 如需詳細資訊,請參閱BinaryFormatter 安全性指南。",請自行參考)
使用序列化進行深複製
首先先將要序列化的類別,也就是會被複製到的相關資料類別(Resume與WorkExperence),
加上序列化屬性標籤,告知編譯器這兩個類別會被序列化,
且WorkExperence也不需要時做ICloneable介面。
並在Resume中的Clone() 改成序列化複製。
結果可以發現一樣達到深複製的效果。
如果我參考的物件內又有包含參考的物件呢?
透過序列話也沒有問題,請參考 Chapter_9.4 ,
我把WorkExperence內的company由string型別改為Company類別,
因此使用者複製Resume時會複製第一層的Resume,
再來是Resume所參考的第二層Company物件,
即使是多層的物件參考,透過序列化的複製一樣能達成。
疑?怎麼沒有實作ICloneable介面?
不需要繼承ICloneable介面的好處是什麼?
如果繼承了ICloneable介面,代表後續其他的子類別也必須要實作該介面,
如果有其他方法可以取代ICloneable,代表我們就不必被強制繼承該介面實作Clone方法。
但實作介面也是有好處的,當別人接手你的code時,一目了然這個類別具有什麼功能。
適用情境
目前在自動化設備想不到使用情境(代表我自己不常使用到),
可能會這樣複製大多用在資料的使用,
例如某個時間點的狀態儲存,在往後的流程中如果想要回復到當初儲存的狀態,
就能夠把這個儲存狀態作為恢復的依據。
沒有留言:
張貼留言