Java一定要用getter/setter嗎?
相信許多寫過 Java 的人,心中或多或少都有一個疑問,Java 的 getter / setter 真的有這樣寫的必要嗎?
今天就讓我們來探討一下這個問題。
目次
前言
getter / setter 只是一種慣例嗎?甚至可以說是 Java 的陋習嗎?
把只需要簡單存取的成員變數宣告成 public 會破壞封裝嗎?那麼,為 private 成員提供 public getter / setter 就不會破壞封裝嗎?
使用 getter / setter 是預留未來的可擴充性嗎?如果我確定完全不需要擴充,或是我有別的方法擴充呢?
慣例、避免破壞封裝、預留可擴充性,應該都是主張使用 getter / setter 的常見理由,但我認為 Java getter / setter 存在的意義應該從別的層面探討。
本文內容純屬個人淺見,並非是公認的標準答案,僅供參考。
誰適合閱讀
不限,但有學習過或使用過物件導向程式語言(OOP)的人可能會比較感興趣。
getter / setter 是必要的嗎?
如果我有一個這樣的 Class:
public class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
Dog 的成員 name 宣告成 private,但同時也提供兩個public 方法讓外部存取 name。
從邏輯上來說,這無異於直接將 name 宣告成 public,讓外部能夠直接存取 name。
既然如此,特地把 name 宣告成 private,再提供兩個 public 方法讓外部去存取它,有什麼意義嗎?
我認為還是有,下面我會說明我的理由。
getter / setter 的設計哲學
在物件導向程式語言裡,一個 Class 的成員變數(instance variables)意味著這個 Class 的實例(Instance)能具備什麼狀態,比如名字、年齡、性別。
而 Class 的方法(method)則透露這個 Class 具備什麼能力,比如走、跑、吃。
當我們把 Class Dog 的成員變數 name 宣告成 private,代表我們想表達這個變數屬於 Dog 的內部狀態,如果外部想要存取它的值,應該使用 Dog 提供的方法來操作。
所以 getName() 跟 setName() 就是在讓外部知道,Dog 可以告訴你他的名字,也允許你修改他的名字。
但如果我們今天把 Class Dog 改寫成這樣:
public class Dog {
public String name;
public Dog(String name) {
this.name = name;
}
}
這個 Class 就不具備任何能力,雖然他確實存在一個成員變數,而且宣告成 public,允許外部存取他的值,可是卻無法表達 name 的用法。
設計這個 Class 的人,真的是希望我們去存取 name 嗎?還是有其他目的?
如果沒有 method,這種猜疑就可能發生,尤其是對於那些只要稍微動一行程式碼就可能釀成悲劇的大型專案,想必會變得更加寸步難行。
這時候如果加上 getter / setter,就能明確表達意圖。
這個行為看起來像多此一舉,是因為我們從邏輯的層面去看待這個問題。
如果我們換個維度,從設計哲學的角度去思考,就可能得出 getter / setter 有一定的合理性這樣不同的答案。
方便追蹤程式碼
即使不從哲學的角度來看 getter / setter,單從追蹤程式碼的角度來說,getter / setter 也存在一定的方便性。
如果我們有使用 getter / setter,我們就可以很輕易在一個大型專案裡,找到程式在什麼地方進行了賦值或取值,這可以提高程式碼的可維護性。
如果只是單純把 Class 的成員變數宣告成 public,那就無法簡單做到這件事。
一個變數可能在 100 個地方被引用,其中只有 1 個地方是賦值,其他全部是取值。
如果我們現在只關心它的值是在哪裡被修改的,要想在 100 個搜尋結果中,找到唯一一個賦值的地方,勢必比較麻煩。
但如果有 setter,馬上就可以找到了。
在修改一個大型專案時,確認有哪些地方會賦值、哪些地方會取值,是確保程式運作能符合預期、甚至是保證多執行緒安全(thread-safe)常常需要做的工作之一。
小提示:搜尋一個方法或一個變數在哪裡被引用,已經是大部份IDE的必備功能,比如 VSCode 可以對變數按F12來搜尋,在 Eclipse 可以用 Ctrl + Shift + G。
結語
一項設計不會無緣無故就是合理的,每個技術的出現都是為了回應某個問題,如果我們不嘗試擴大思考格局、不去想像某種設計是在試圖解決什麼問題,就可能讓一項有用的設計看起來一點都不合邏輯。
然而,合理並不表示是最好的,如果我們能認同一個設計理念,卻不滿意具體作法,就表示重新設計的時候到了,重複造輪子並不會沒有意義。
「認知」會改變一個人的行為,包括如何設計程式。
我們所寫下的每一行程式碼、規劃的程式模塊或程式架構,都透露著我們如何認知程式設計、如何認知我們所面對的問題的本質。
擁有不同認知的人,在面對同一個問題時,會採取完全不同的決策。
也許這些決策在邏輯上都是對的,但在哲學上卻可能有合理性的高低之分,只有當時間拉長了,或是問題的範疇某天忽然改變了,才能發現原本的設計有什麼優缺點。
程式設計不是完全的邏輯問題,如果我們在訓練邏輯思維的同時,也能留意哲學思維的重要性,長遠來看一定會帶給我們正面的影響。
希望有帶給大家一些不一樣的想法~