1. 程式人生 > >QQA: 為什麼 java 中要寫 getter/setter?

QQA: 為什麼 java 中要寫 getter/setter?

java 有一個不成文的規定,如果要訪問一個類的 private 欄位,就需要寫 getter/setter 方法。但我們在其它語言卻很少見到類似的約定,為什麼?

  • 它是“封裝”的體現,對外隱藏了具體實現,允許之後對屬性的訪問注入新的邏輯(如驗證邏輯)。
  • 一些語言,如 python,提供了機制允許我們更改訪問屬性的邏輯,因此不需要手工寫 getter/setter。

#getter/setter 是對“屬性訪問”的封裝

假設我們寫了下面這段程式碼,直接訪問類的 public 欄位:

class Person {    public String name;}// callerString name = person.name;
person.name = "Java";

之後我們認為 name 屬性只能是字母,不能包含其它的字元,上面這種實現中,我們就需要更改所有 caller 呼叫 person.name = ... 的程式碼。換句話說,類 Person 暴露了實現的細節(即欄位 person)。

那麼如果一開始就使用了 getter/setter,則我們不需要改變任何 caller,只需要在 setName 函式裡增加相應的邏輯即可。

class Person {    private String name;    public String getName() {        return this.name;
} public void setName(String name) { validate_name(name); // the newly added validation logic this.name = name; }}// callerString name = person.getName();person.setName("Java");

所以,通過這層封裝,之後如果有需要,我們甚至可以更改欄位的名字,型別等等。這就是封裝的好處,而 getter/setter 這種寫法能讓我們為將來可能的修改做好準備。

#其它語言裡的 getter/setter

getter/setter 的作用是為“屬性的訪問”(即 x.fieldx.field = ...)提供日後修改的可能。一些“比較新”的語言就預設提供了這種能力。

Python 中提供了 Descriptor 的機制。在 Python 中,可以認為當訪問物件的屬性時,等價於呼叫物件的 __get__()__set__() 方法,因此我們可以覆蓋這兩個方法來修改訪問的邏輯。

同樣的,Kotlin 在定義 properties 也可以自定義的 getter/setter 方法來修改屬性訪問的邏輯。

這裡想說明的是,getter/setter 其實應該是預設實現,然後有需要時再覆蓋,而不是每次都手工實現。

#社群與約定

也許你會問,封裝其實叫什麼名字都行,為什麼非要叫 getXXXsetXXX 呢?這其實是 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。這些觀點背後都藏著一些軟體的設計思維。例如怎樣設計類的介面,如何實現封裝,這些都是後續需要學習思考的內容。