Getter Eradicator
2006 年 2 月 22 日
當他們看到 getter 方法時,你可以從他們嘴角左邊的抽動看出,他們會迅速拔出他們的戰斧,並在從一個隨即昏厥的類別中無情地砍下另一個 getter 時發出滿足的歡呼,而這個類別會在英勇的 Getter Eradicator 的腳下欣喜若狂地昏厥。
好吧,也許我回到英國啤酒的懷抱讓我有點太激動了,但 Chris 的 溫和調整 觸動了我的一個小抱怨。我經常遇到一些人告訴你避免在類別上使用 getting 方法,並將此類事情視為封裝的違規,Allen Holub 的文章 是最著名的文章之一。
一般的理由是 getter 違反了封裝。如果我有一個保齡球員類別,其中包含 overs、runs 和 wickets 的欄位,那麼新增 getter(getOvers、getRuns、getWickets)比僅將欄位設為公開好不了多少。
這個論點有一定的道理,我當然建議你在真正需要之前不要撰寫存取器,但它也帶來了錯失封裝重點的危險。對我來說,封裝的重點並不在於隱藏資料,而在於隱藏設計決策,特別是在這些決策可能必須變更的領域。內部資料表示就是一個例子,但不是唯一的,也不是總是最好的。用於與外部資料儲存庫通訊的通訊協定是一個封裝的好例子,它更注重傳送至該儲存庫的訊息,而不是任何資料表示。當你在思考封裝時,我認為詢問自己「你正在隱藏哪一部分的可變性以及為什麼」會比「我是否正在公開資料」更好。(Craig Larman 為我寫了一篇 精彩的專欄。)
儘管封裝的防禦是 getter 消滅者的常見口號,但我認為他們真正的動機更為合理和務實。在 OO 語言中,有大量的程式碼在設計上是程序化的。OO 社群可能在現代語言由物件主導的意義上「獲勝」,但他們在 OO 程式設計尚未廣泛使用的意義上仍未獲勝。因此,仍然常見到程序從物件中提取資料來執行某些操作,而這種行為更適合放入物件本身中,這違反了實用程式設計師的原則「告訴,不要詢問」。如果你有 getter,你只能執行這種程序化程式設計,因此告訴人們擺脫 getter 有助於促使他們將行為移至正確的位置。
我對此動機非常認同,但我擔心只告訴人們避免使用 getter 是個相當粗糙的工具。物件確實需要透過交換資料來進行協作,這會導致對 getter 產生真正的需求。
如果我們要找一個簡單的經驗法則,我比較喜歡一個我第一次從 Kent Beck 那裡聽到的法則,就是永遠要小心某些程式碼在同一個物件上呼叫多個方法的情況。這會發生在存取器和更合理的指令上。如果你要求一個物件提供兩個資料,你能用單一的要求來取代這個要求,以取得你正在計算的資料嗎?如果你告訴一個物件做兩件事,你能用單一指令來取代它們嗎?當然有很多情況你不能這麼做,但永遠值得問問自己這個問題。
另一個麻煩的良好警訊是資料類別,一種只有欄位和存取器的類別。這幾乎總是麻煩的徵兆,因為它缺乏行為。如果你看到其中一個,你應該永遠保持懷疑。找出誰使用資料,並嘗試看看是否可以將其中一些行為移到物件中。在這些情況下,問問自己「我可以擺脫這個 getter 嗎?」可能會很有用。即使你不能,提出這個問題可能會導致一些良好的行為移動。
在物件之間配置行為是物件導向設計的精髓,因此就像任何設計一樣,沒有硬性規定,而是權衡取捨。將行為放在與資料同一個類別中,Craig Larman 稱之為「資訊專家」,是第一個要考慮的選擇。但這不是唯一的途徑。分層通常會勝過這個,四人幫模式中的許多模式會將資料與行為分開,以滿足特定需求。一個好的經驗法則就是,一起改變的事物應該放在一起。資料和使用它的行為通常會一起改變,但你經常會看到其他更重要的群組。