注意我說的是依賴,不是說完全不該用。
你對下面的情境很熟悉嗎?
現在你有一個Enemy類別,你希望它能夠知道玩家的位置,因此他需要擁有Player物件的transform。因為Enemy實體通常都是執行期產生的,所以你不會想用GameObject.Find(如果你還不知道,你現在該知道了),也不可能使用Serialized Field。 通常下一瞬間就是,「把Player變成singleton」。嗯...好吧!的確如果你的Player實體只會有一個,那麼使用singleton好像也是可以接受的。
現在你的Enemy到了Player附近,攻擊,然後成功了,要傳送傷害只需要透過Enemy類別內某一行的Player.Instance.GetDamage( value )。很好,看起來挺簡單的,singleton帶來的好處。
Player需要恢復它的生命力,理所當然需要恢復藥水,恢復的數值寫在InventoryTable內,因此需要拿到這個實體。正常來說這個腳本不會是MonoBehaviour,Player類別也不可能自己製造出InventoryTable的實體。 所以「把InventoryTable變成singleton」就變成了個理所當然的選項。嗯...好吧!的確InventoryTable實體也只會有一個,那麼使用singleton好像也是可以接受的。
Player使用完藥水後,要從Backpack裡扣除一瓶恢復藥水。根據劇本需求,我們假設Player並不直接擁有Backpack的實體(通常也是沒有的)。前面我就不贅述了,反正「把Backpack變成singleton」又變成了個理所當然的選項……。
等等,你有認真想過嗎?
仔細想想,你會隨便將一個變數設定成全域變數嗎?
應該是不會吧。(如果會,所有的軟體工程師都會詛咒你的)
那麼仔細想想,singleton幾乎跟全域變數一樣,怎麼會隨便就把東西做成singleton呢?
良好程式碼的3個特性:可讀性、可維護性、可擴充性
在大量使用singleton的專案,或許可擴充性是挺高的(注意我說的是或許)。但我相信維護性就不怎麼樣了。
大量使用singleton會有什麼問題嗎?舉幾個例子:
你有沒有debug時讓你在各個腳本之間巡迴的經驗?
通常你會巡迴於各個腳本,主要是因為當bug發生時,你不知道bug是怎麼發生的,在哪裡發生的。若你沒有好好控制singleton的使用,那麼singleton就會豐富你的debug過程,在你的腳本與腳本之間搭起跨越系統的橋樑。
你有沒有曾經突然發現自己的程式碼需要整理(尤其是當你沒有重構的習慣時),但程式碼已經難以改動的經驗?那是因為singleton造成的大量相依性與耦合度,使得你的程式碼看起來像個由一堆singleton所組成的克蘇魯麵條怪物,看到了實在也不知道怎麼重構。
你有沒有到了開發中後期,常常出現不得不使用singleton的情況?因為軟體架構已經很差,讓你在後期很難寫程式,這時候singleton是不是一個很誘人的選項?吸過一次毒,就會想再吸第二次,對吧?
需要再提醒一下,並不是說不要用singleton,而是不要依賴singleton。
有些工程師認為完全不應該使用singleton,我完全可以理解這個想法。不要讓惡魔有出現的可能性。我個人比較偏向,要知道什麼時候該用,什麼時候不該用。如果情況允許,評估使用後不會造成後期維護的影響,就審慎使用。注意,請把singleton造成的影響當成全域變數來看待。
所有訓練有素的工程師都知道設計模式並不是萬靈丹,而是一種取捨,毫不思考的使用設計模式就會使你的開發過程如同惡夢。訓練有素的工程師知道在開發前先初步規劃軟體架構,並且知道什麼時候該用什麼設計模式,什麼時候不該用。
下次被逼得想要使用singleton前,先考慮一下。
你好,感謝你用簡單的幾句話描寫處,過度使用Singleton的問題,雖然知道使用Singleton太隨意地使用會出現問題,但總是無法用言語去描述實際上發生的問題,最近在看相依性注入,也是感謝你所提出的幾個盲點,我對這東西算是有個基本上的認識了。