getter 和 setter 方法有什麼意義?

編程菜鳥求掃盲:

一般在書上看到都解釋都是 把成員變數直接暴露在外不符合OOP的封裝性原則,不安全,應該使用getter和seter方法來取值和賦值。但是沒有解釋為什麼不符合OOP的封裝性原則,為什麼不安全,一個成員變數不就是取值和賦值這麼兩個操作嗎,還能幹什麼,暴露出來又怎麼樣?


的確可以暴露,如果1. 所有內外代碼都是你自己寫;2. 這個模塊再也不改了;3. 不會繼承它,或者繼承但不改變語義。

David John Wheeler有一句名言:

「All problems in computer science can be solved by another level of indirection.」

getter、setter就是個很好的中間層。

直接摘錄stackoverflow上一個不錯的總結:oop - Why use getters and setters?

  1. 這兩個方法可以方便增加額外功能(比如驗證)。
  2. 內部存儲和外部表現不同。
  3. 可以保持外部介面不變的情況下,修改內部存儲方式和邏輯。
  4. 任意管理變數的生命周期和內存存儲方式。
  5. 提供一個debug介面。
  6. 能夠和模擬對象、序列化乃至WPF庫等融合。
  7. 允許繼承者改變語義。
  8. 可以將getter、setter用於lambda表達式。(大概即作為一個函數,參與函數傳遞和運算)
  9. getter和setter可以有不同的訪問級別。


別的都不說,就一個,能在讀寫數據的時候下斷點。


對於線程安全有很大意義。如果是安全的getter、setter是可以在多個線程里用的,可以配合其它線程安全的方法一起使用。


前面的回答結合起來看都不錯。來打個醬油:

getter / setter 方法的好處前面都說了,但是在具體語言里使用起來的「麻煩程度」可以很不同。

前面有回答提到了Java:沒有原生的getter / setter 語法,這只是一個Java Beans的命名規範。

也有提到C#的property、ActionScript 3等語言:有原生的getter / setter語法

然後也有像Ruby這樣,完全無法直接對外暴露欄位,必須通過getter / setter方法來訪問,而且標準庫自帶元編程機制簡化欄位+getter/setter聲明。

Eiffel也挺有趣:它的語言設計遵循「統一訪問原則」(Uniform access principle),也就是說一個屬性訪問的背後無論是由欄位直接支持,還是由getter/setter方法支持,語法都一樣;在通過繼承來覆寫屬性時,無論基類的某個屬性用了欄位還是方法來支持,派生類都可以自由選擇用欄位或方法來覆寫之。這樣的自由度非常高,比C#、ActionScript 3等其它有原生getter / setter語法支持的語言更自由一些。

這個意義上說,ECMAScript 5跟Eiffel的設計有那麼一點相似,也可以算遵循了「統一訪問原則」。


同意@仲晨 的回答,但是這裡還想從另外一個更宏觀的角度,以我的理解做一點補充。

對於OOP,宏觀上來說,設計者都在試圖做到的一件事情就是如何當好程序中的上帝。通過設計良好的介面(這裡的良好指的是不多也不少)來對外暴露一個對象的能力,使得使用者只需要充分了解介面,就可以了解這個對象所能提供的能力。

而使用「方法」來表達對象的能力,同使用「變數」相比,從宏觀上來說,沒有什麼區別,只是形式上的不同。但使用「方法」來表達介面,更容易體現OOP的一個核心理念「隱藏細節」


getter/setter提供了一些屬性讀取的封裝,可以讓代碼更便捷,例如存儲FirstName和LastName欄位,但是提供FullName屬性的getter。

某些語言的getter可以以無限接近欄位訪問的語法提供屬性的訪問,例如obj.Counter++,而無需obj.setCounter(obj.getCounter() + 1)

setter對於程序的健壯性非常有幫助,在setter里可以直接判斷輸入值是否合法,如果直接暴露欄位就很危險了。

setter中可以打斷點、打日誌,方便開發調試和追蹤程序狀態,還能觸發事件,實現類似onNameChanged這樣的事件。

在C++里沒有getter/setter(別跟我說用宏定義實現啊),也似乎沒有getXXX/setXXX的約定(原諒我C++寫的極少),訪問類的屬性最便捷的方法就是直接暴露一個public欄位,這樣對封裝性是有一定破壞的。與此同時,如上面一個例子,FirstName和LastName是欄位,而FullName卻是一個getFullName的方法,不統一,怪怪的。

C#的getter/setter雖然終究是編譯成了getXXX/setXXX方法,但這絕對不是簡單的語法糖,是有語言意義的,它讓Property(屬性)和Field(欄位)因此有了區分,他們倆在meta data里是分開的,可以單獨反射出來的。

與此同時,C#中同一個Property的getter和setter可以有不同的暴露程度,比如public getter配合private setter是一種挺常見的組合,這對於OOP所需要的封裝是有幫助的。

JAVA沒有提供語言特性和meta data級別的getter/setter,getXXX和setXXX是一種約定,當然因為這個約定接受度非常廣泛,現在很多IDE也完全能把它當做Property來看。但是無疑不像C#那樣方便,比如你反射遍歷一個類的屬性的時候,在C#中只需要反射Property,而JAVA中需要反射Method,然後用命名規則去匹配,這感覺怪怪的。


getter和setter的有意義的前提是你要知道什麼時候該用,什麼時候不該用。

脫離開這個前提,getter和setter的意義基本上就只體現在年底KPI考核的時候,也許就因為你多寫了一些getter/setter,LOC產出高過了某個閾值而導致你多發了一些獎金。

至於什麼時候該用什麼時候不該用,當你寫過足夠多的代碼、改過足夠多的bug、挨過足夠多的罵之後自然就知道了,所有所謂的「編程規則」莫不如此。


一個變數在對象裡面一般是有實際意義的,也有一個取值範圍。這個取值範圍有可能比變數類型的範圍小(比如說定義了int其實只能取1-10000之間),或者語言本身就是弱變數類型的,編譯器根本不檢查。這時候如果這個變數隨便賦值的話對象的方法一但容錯性不強就會出錯。一般來說在賦值的時候就進行類型檢查(setter)比在每個方法裡面都寫容錯代碼或者進行類型檢查開銷要小。


暴露出來意味著可以被任意操作,如果一個square的width和height是public欄位,可以被改成不一樣的,那麼這個square的狀態就不好了。

在setter里至少還可以做一些限制。

=============================================================

看了第一的回答和Stackoverflow的鏈接,覺得這個說得很好.

Tell, Dont ask. The Pragmatic Bookshelf

"That is, you should endeavor to tell objects what you want them to do; do not ask them questions about their state, make a decision, and then tell them what to do."

直接暴露欄位顯然會更容易寫出ask state, make decision, and tell them what to do的代碼。這種寫法更像過程式而不是面向對象。


(我所說的getter/setter指的是純粹的屬性讀寫方法)

1. 暴露一個屬性(改成public/protected)意味著讀寫同時暴露,所謂的getter/setter,不是說他們一定要同時出現,大多數情況應該只出現一個(或者不出現)。

2. 我對於傳統的getter/setter的看法個人持保留意見。沒有邏輯意義的getter/setter是不應該存在的。前面有人提到說getter/setter可以加驗證加鎖之類的,加了代碼的getter/setter我認為已經不是一個屬性訪問方法了,而是一個具有意義的「類的方法」。

所以我的觀點就是,不應該有「純粹為了讀寫屬性而寫的getter/setter」,類中的所有方法(包括getter/setter)都應該經過精心的設計。這種前提下,如果一個方法碰巧只是做了讀寫,是可以的。

我個人的一個習慣: 有時候getter setter被用的太普遍了,我有時候要寫一個純setter的方法都會特意寫成changeXX, modifyXX之類的而不是setXX,目的就是表示這是設計出來的有邏輯意義的方法,以示區分。

getter是feature envy的前兆。

setter會破壞封裝。


來自《 API design for C++ 》

1. 有效性驗證(可以在setter里檢查設置的值是否在許可區間里)

2. 惰性求值(比如一個成員計算過於耗時,而這個類的用戶(這裡的用戶指其他程序員)不一定需要時,可以在getter方法調用的時候再計算)

3. 緩存額外的操作(比如用戶調用setter方法時,可以把這個值更新到配置文件里)

4. 通知(其它模塊可能需要在某個值發生變化的時候做一些操作,那麼就可以在setter里實現)

5. 調試(可以方便的列印設置日誌,從而追蹤錯誤)

6. 同步(如果多線程訪問需要加鎖的話,setter里加鎖不是很容易么)

7. 更精細的許可權訪問(比如private變數只有getter沒有setter,那客戶對該變數就是只讀了,而類的內部代碼可以讀寫)

8. 維護不變式關係(比如一個類內部要維持連個變數a和b有a = b * 2的關係,那麼在a和b的setter里計算就能維持這樣的關係)

我再說個,還可以不對外暴露內部的數據組織方式,即使類數據的組織結構發生變化也不需要修改外部用戶的代碼。


提到這個問題首先要明確一點。那就是Java中沒有語義級別的getter 和setter方法。

如c#,as3這樣的語言

as3中的 getter和setter方法

private var _age:int;
public function get age():int{
return _age;
}
public function set age(value:int){
_age = value;
}

Java中的getter和setter其實就是兩個方法,將一個變數封裝了一下。一個獲取,一個注入(前提是你不自行進行其他封裝)。而方法的名字遵循一定的命名規則。

那麼基於上面 這個問題從以下幾點討論

1、OOP

從OOP的角度來講,需要對屬性進行下封裝,以進行下更進一步的內部處理,比如你要判斷某一個屬性的傳入值是否合法等。

2、可擴展性

那麼可能你一個類有10個成員變數,但你目前只有一個需要進行封裝。所以從外部看起來你其他的屬性都是直接使用,x.name x.age等等

目前看來沒什麼問題,但以後可能其他的成員變數也需要你封裝了,這時候你不僅要修改這個類的內部實現,而且連介面都變了。有相關調用的地方都要統一改。就不太好了。

這裡的問題是在於java沒有語義級別的getter和setter,java里只是方法。在c#和as3這樣的語言中就不用考慮這個問題了。

所以為了以後的擴展性,還是都用getter和setter方法封裝下比較好,無論此時此刻你是否需要這樣的封裝。

3、命名規範

如果一個框架提供的API在獲取屬性的時候一會直接獲取。一會只能getXXX這樣獲取。你認為這樣的API在命名上足夠規範和統一么?答案肯定是不夠。

所以為了統一的規範,還是都用getter和setter封裝過比較好。而且看java原生api中也是鼓勵這樣做的。


現在這個問題很好回答了 。

意義就是 Vue.js


getter和setter是用來約束屬性的設置和獲取的。

假如getter和setter什麼都不約束的話,就不要寫了。

封裝不是簡單的用訪問許可權修飾,然後設置對外的訪問方法。封裝是把複雜的內部邏輯用訪問許可權來隔絕外部調用,以避免外部的錯誤調用影響到內部的狀態,同時也是把內部複雜的邏輯結構封裝成高度抽象的簡單調用,以方便使用者的調用。

以上個人理解,有錯誤歡迎指正。


根據我的經驗是為了保留類的擴展性。

因為方法可以被子類重寫(甚至你可以添加棄用標記並在內部拋出錯誤以到達「刪除」父類方法的效果), 但屬性不行。一個屬性暴露的類,意味著它的子類也必須有該屬性,少了幾分靈活。


我感覺就是除了所謂的隱藏細節這種理論上的,和訪問控制這種實用性的原因外,還有就是為了格式統一:對象的「.」後面,跟的全是方法!不建議直接拖個變數。(數組的length是什麼鬼……)


補充一點:

大家都用了之後, 你使用別人的類, 去找哪些類可以賦值時候, 就

class名.set

然後IDE就自動提醒你, 哪些可以使用啦````

get也是同樣的道理


Effective Java上說,提供access method(getter/setter)可以保留將來改變該類內部呈現的靈活性(to preserve the flexibility to change the class』s internal representation.)。如果共有類暴露了數據域,將來改變內部呈現就不可能了,因為client code已經被傳播出去了。如果類是private的或是package-private的就無所謂。


創建Hook函數,比如說很久以前的JSON劫持就是對Object.prototype設置了setter來獲取用戶的敏感信息


推薦閱讀:

我們為什麼需要React?
學習編程的好方法——控制台遊戲
git:一個本地分支可以對應多個遠程分支么?
High Flexibility Remote Development

TAG:編程語言 | 編程 | 面向對象編程 |