對象屬性(property)和 getters , setters 方法
「須要爲一個對象的屬性添加 Getters / Setters 方法」而提出爲何?由此而進行深刻思考。java
它是字段(field)
在 Java 中咱們都知道如何在類(Class)中聲明一個成員屬性(field)。git
public class HikariConfig { public long connectionTimeout; public long validationTimeout; }
當咱們須要設置對象的屬性值時,咱們能夠直接使用 =
賦值。typescript
public class HikariConfigTests { public static void main(String[] args) { var config = new HikariConfig(); config.connectionTimeout = 250; config.validationTimeout = 250; } }
若是咱們須要在設置 connectionTimeout
屬性時,作一些賦值校驗。好比:connectionTimeout 不能小於 250ms 。編程
public class HikariConfigTests { public static void main(String[] args) { var config = new HikariConfig(); var connectionTimeoutMs = 250; if (connectionTimeoutMs < 250) { throw new IllegalArgumentException("connectionTimeout cannot be less than 250ms"); } config.connectionTimeout = connectionTimeoutMs; } }
屬性(property)具備封裝性
面向對象有三大特性:繼承、封裝、多態。服務器
咱們應該已經發現校驗 connectionTimeout 的邏輯(代碼)被放置在 HikariConfig 對象自身以外,但從面向對象的角度來講如校驗屬性的代碼應該放在 connectionTimeout 上,可是字段(field)不具有封裝性。less
若是你發現了這個問題,那麼面向對象的設計者們也同樣會發現這個問題。編程語言
當聽到屬性這個詞時,你想到的是什麼呢?this
- 你可能想到的是字段(field),由於 field 經常會被翻譯爲成員屬性(field)。
- field 真正要表達的意思是:一塊存放數據區域。
一個對象是由屬性和操做組成的。操做能夠被封裝成一個方法:翻譯
public interface Runnable { void run(); }
若是操做能夠被封裝成方法,那麼如何封裝屬性呢?設計
現代的編程語言爲使用者提供了一些語法糖來封裝屬性,好比:C# , Kotlin , Typescript , Scala 等等。
在 Kotlin 中咱們可使用 get
和 set
關鍵字來封裝屬性:
class HikariConfig { var connectionTimeout: Long = 0 set(value) { if (value < 250) { throw IllegalArgumentException("connectionTimeout cannot be less than 250ms") } field = value } }
在 Kotlin 中使用屬性:
fun main() { val config = HikariConfig() config.connectionTimeout = 250 }
在 Typescript 中咱們可使用 get
和 set
關鍵字來封裝屬性:
class HikariConfig { #connectionTimeout: number public get connectionTimeout() { return this.#connectionTimeout } public set connectionTimeout(connectionTimeout) { if (connectionTimeout < 250) { throw new Error("connectionTimeout cannot be less than 250ms"); } this.#connectionTimeout = connectionTimeout } }
在 Typescript 中使用屬性:
const config = new HikariConfig() config.connectionTimeout = 250
在 Java 中並無爲屬性(property)提供 get
和 set
關鍵字,而是將其設計成方法。 使用 getXxx
方法來模擬 get
關鍵字和使用 setXxx
方法來模擬 set
關鍵字。
class HikariConfig { private long connectionTimeout; public long getConnectionTimeout() { return this.connectionTimeout; } public void setConnectionTimeout(long value) { if (value < 250) { throw new IllegalArgumentException("connectionTimeout cannot be less than 250ms"); } this.connectionTimeout = value; } }
原本在擁有 get
和 set
關鍵字的編程語言裏,你們只是對 property 與 field 有些混淆,這樣的混淆仍是能夠很簡單的解釋清楚。可是在 Java 中因爲直接使用方法(getXxx
, setXxx
)來封裝屬性(property)使得你們對 field , property 和 method 三者混淆起來。在有些時候你們不知道 getXxx
和 setXxx
方法是在作對象的屬性(property),因此不少人誤認爲字段(field)即是屬性(property)。尤爲是在應用系統開發中許多模型的屬性不須要作多餘封裝,只是直白的存在。
當把字段(field)誤認爲是屬性(property)之後,在遇到須要爲某一個對象的屬性進行封裝時,每每會使用其它方法來解決。好比:changeXxx
方法。
在擁有 get
和 set
關鍵字的編程語言裏,在使用 get
或者 set
關鍵字時,在編譯器在編譯代碼時,依然會將 get
或者 set
關鍵字所作的對屬性(property)的封裝轉換成讀(read
, get
)方法或者寫(write
, set
)方法,因此get
和 set
關鍵字只是對 getXxx
和 setXxx
方法的一種語法糖。
在 Typescript 中,編譯器最終會將 get
或者 set
關鍵字最終編譯成這樣:
Object.defineProperty(config, "connectionTimeout", { configurable: false, enumerable: false, set: function (connectionTimeout) { if (connectionTimeout < 250) { throw new Error("connectionTimeout cannot be less than 250ms"); } this.#connectionTimeout = connectionTimeout; }, get: function () { return this.connectionTimeout; } })
在 Kotlin 中,編譯器最終會將 get
或者 set
關鍵字編譯成這樣:
class HikariConfig { private long connectionTimeout; public long getConnectionTimeout() { return this.connectionTimeout; } public void setConnectionTimeout(long value) { if (value < 250) { throw IllegalArgumentException("connectionTimeout cannot be less than 250ms"); } this.connectionTimeout = value; } }
在其它擁有 get
或者 set
關鍵字的編程語言(C# , Scala)裏同樣會將其編譯成某種格式的方法來完成對屬性的封裝性。
屬性具備讀(read)和寫(write)權限
在 Java 中提供了四個訪問控制修飾符( public , protected , default , private ),他們能夠修飾類(class)、方法(method)以及字段(field)。須要更深刻的瞭解到它們只是在控制必定的範圍,好比在建立(new)一個對象時,是在控制能夠在哪一個包(package)內去建立這個對象。好比在使用方法時,也是在控制能夠在哪一個包內去使用這個方法。一樣在使用字段(field)時也是在控制能夠在哪一個範圍內使用。
這些訪問(access)控制修飾符應該做用在被修飾的動做(動詞)上,而不是名稱(名詞)。好比:對象的建立,方法的調用,字段的得到與設置。建立(new)、調用(invoke)、得到(get)、設置(set)這樣的動做都須要經過訪問修飾符作到精確控制。對於一個類(class)在使用時只有一個建立(new)的動做,一樣的使用方法時也只有一個調用(invoke)的動做,不須要再次精確細分。而對於字段(field)在使用時有兩個動做:得到(get)和設置(set),而字段(field)自己在處理這兩個動做時並無辦法作到細分。
如今的問題是同一個訪問控制修飾符同時控制對某一個字段(field)的兩個動做( get , set ),而這個問題須要發現者仔細思考:
class HikariConfig { connectionTimeout: number } const config = new HikariConfig() config.connectionTimeout = 100 // Set const timeout = config.connectionTimeout // Get
若是此時把 public
修改成 protected
,那麼操做 connectionTimeout
的兩個動做( get , set )的訪問控制將所有變成 protected 。
class HikariConfig { protected connectionTimeout: number }
咱們能夠發現一個字段的兩個動做( get , set )的訪問權限被混合到了一塊兒,這帶來了什麼問題呢?
- 一個對象的屬性只是想對外提供公共讀(public read),對外不提供公共寫(private write)。
- 一個對象的屬性只是想對外提供公共寫(public write),對外不提供公共讀(private read)。
- ......
簡單來講就是:可讀、可寫、只讀、只寫、不可讀寫。
若是一個對象的屬性只是想對外提供只讀屬性(注意是對外,對象的內外有區別),而被 public
修飾的字段將帶來的是 get
和 set
都具備可讀寫的權限,這就使得使用者能夠設置(set)這個字段。這將給對象帶來意向不當的後果,有多是破壞性的後果。
所以爲一個對象的屬性( get
, set
) 提供不一樣訪問控制是有必要的。
class HikariConfig { #connectionTimeout: number // private public get connectionTimeout() { // public , protected , default , private return this.#connectionTimeout } public set connectionTimeout(connectionTimeout) { // public , protected , default , private this.#connectionTimeout = connectionTimeout } }
屬性能夠無讀(寫)操做
一個屬性(property)有兩個操做:讀(read)和寫(write)。能夠將一個對象的屬性的讀寫權限修改成 private 。私有的訪問控制權限並不意味着不存在,只是代表這個屬性只能夠在這個對象內部使用,對外不可以使用。一樣的屬性能夠具備無讀(寫)操做。
無寫(write)操做:
class HikariConfig { #connectionTimeout: number public get connectionTimeout() { return this.#connectionTimeout } // 沒有 set 方法,無 set 與私有 set 的區別。 }
無讀(read)操做:
class HikariConfig { #connectionTimeout: number // 沒有 get 方法,無 get 與私有 get 的區別。 public set connectionTimeout(connectionTimeout) { this.#connectionTimeout = connectionTimeout } }
一個屬性不能同時沒有讀和寫方法(操做),若是同時沒有也就代表這個屬性不存在。
區分屬性(property)和字段(field)
封裝性和讀寫訪問控制是屬性(property)和字段(field)最根本的區別。在區分屬性和字段的區別時,是依據他們的所具備的功能來判斷的。
尤爲是在具備 get
和 set
關鍵字以及對屬性(property)和字段(field)沒有區分(如:命名規範、使用方式)的編程語言裏,區分屬性和字段只須要簡單的經過是否具備封裝性和讀寫控制來區分。
屬性(property) | 字段(field) | |
---|---|---|
封裝性 | 具備封裝功能 | 不具備封裝功能 |
讀寫控制 | 能夠細分控制 | 不能細分控制 |
實體對象屬性校驗方式
嘻嘻,過兩天再說。~~~~
開源電商
Mallfoundry 是一個徹底開源的使用 Spring Boot 開發的多商戶電商平臺。它能夠嵌入到已有的 Java 程序中,或者做爲服務器、集羣、雲中的服務運行。
- 領域模型採用領域驅動設計(DDD)、接口化以及面向對象設計。
項目地址:https://gitee.com/mallfoundry/mall
總結
屬性(property)與字段(field)有區別,總的來講是兩個方面:封裝性和訪問控制。
一個對象是由屬性和方法組成的,因此認識屬性時須要知道屬性是具備封裝性的。而不能只認識到只有方法須要封裝,屬性同樣須要封裝。、當屬性和方法都具備封裝性時,在使用具備屬性和方法的對象時纔不會極化。