QQA: 為什麼 java 中要寫 getter/setter?
java 有一個不成文的規定,如果要訪問一個類的 private 欄位,就需要寫 getter/setter 方法。但我們在其它語言卻很少見到類似的約定,為什麼?
- 它是“封裝”的體現,對外隱藏了具體實現,允許之後對屬性的訪問注入新的邏輯(如驗證邏輯)。
- 一些語言,如 python,提供了機制允許我們更改訪問屬性的邏輯,因此不需要手工寫 getter/setter。
#getter/setter 是對“屬性訪問”的封裝
假設我們寫了下面這段程式碼,直接訪問類的 public 欄位:
class Person { public String name;}// callerString name = person.name; |
之後我們認為 name
屬性只能是字母,不能包含其它的字元,上面這種實現中,我們就需要更改所有 caller 呼叫 person.name = ...
的程式碼。換句話說,類 Person
暴露了實現的細節(即欄位 person)。
那麼如果一開始就使用了 getter/setter,則我們不需要改變任何 caller,只需要在
setName
函式裡增加相應的邏輯即可。
class Person { private String name; public String getName() { return this.name; |
所以,通過這層封裝,之後如果有需要,我們甚至可以更改欄位的名字,型別等等。這就是封裝的好處,而 getter/setter 這種寫法能讓我們為將來可能的修改做好準備。
#其它語言裡的 getter/setter
getter/setter 的作用是為“屬性的訪問”(即 x.field
與 x.field = ...
)提供日後修改的可能。一些“比較新”的語言就預設提供了這種能力。
Python 中提供了 Descriptor
的機制。在 Python 中,可以認為當訪問物件的屬性時,等價於呼叫物件的 __get__()
和 __set__()
方法,因此我們可以覆蓋這兩個方法來修改訪問的邏輯。
同樣的,Kotlin 在定義 properties 也可以自定義的 getter/setter 方法來修改屬性訪問的邏輯。
這裡想說明的是,getter/setter 其實應該是預設實現,然後有需要時再覆蓋,而不是每次都手工實現。
#社群與約定
也許你會問,封裝其實叫什麼名字都行,為什麼非要叫 getXXX
及 setXXX
呢?這其實是
JavaBeans
里約定的(7.1 節)。甚至從某種角度來說 getter/setter 的目的也不是為了封裝,而只是一個約定,使框架能識別 JavaBeans 中的 property。
在實際工作中你會發現,90% 以上的 getter/setter 在未來並不會被用來增加邏輯什麼的。所以“封裝”的作用理論上是好的,但實際被使用到的頻率特別低,反而增加了許多無用的程式碼。
另一方面,隨著使用 getter/setter 使用的增加,且由於絕大多數 getter/setter 並不會增加額外的邏輯,使得人們開始習慣於假設 getter/setter 不會有額外邏輯。所以如果你想在 setter 里加一些額外的邏輯時,反而要注意會不會讓使用的人感到吃驚。
#寫在最後
Getter/Setter 這個話題看上去似乎很簡單,它的背後卻有很多可以深究和思考的內容的。有人說 Getter 沒關係,可怕的是 Setter;也有說現在lombok 這麼方便,用 Getter/Setter 有利無害;也有人說盡量避免使用 Getter/Setter。這些觀點背後都藏著一些軟體的設計思維。例如怎樣設計類的介面,如何實現封裝,這些都是後續需要學習思考的內容。