領域驅動設計(DDD):對象屬性(property)和 getters , setters 方法

對象屬性(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 中咱們可使用 getset 關鍵字來封裝屬性:

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 中咱們可使用 getset 關鍵字來封裝屬性:

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)提供 getset 關鍵字,而是將其設計成方法。 使用 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;
    }
}

原本在擁有 getset 關鍵字的編程語言裏,你們只是對 property 與 field 有些混淆,這樣的混淆仍是能夠很簡單的解釋清楚。可是在 Java 中因爲直接使用方法(getXxx , setXxx )來封裝屬性(property)使得你們對 field , property 和 method 三者混淆起來。在有些時候你們不知道 getXxxsetXxx 方法是在作對象的屬性(property),因此不少人誤認爲字段(field)即是屬性(property)。尤爲是在應用系統開發中許多模型的屬性不須要作多餘封裝,只是直白的存在。

當把字段(field)誤認爲是屬性(property)之後,在遇到須要爲某一個對象的屬性進行封裝時,每每會使用其它方法來解決。好比:changeXxx 方法。

在擁有 getset 關鍵字的編程語言裏,在使用 get 或者 set 關鍵字時,在編譯器在編譯代碼時,依然會將 get 或者 set 關鍵字所作的對屬性(property)的封裝轉換成讀(read , get)方法或者寫(write , set)方法,因此getset 關鍵字只是對 getXxxsetXxx 方法的一種語法糖。

在 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 修飾的字段將帶來的是 getset 都具備可讀寫的權限,這就使得使用者能夠設置(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)最根本的區別。在區分屬性和字段的區別時,是依據他們的所具備的功能來判斷的。

尤爲是在具備 getset 關鍵字以及對屬性(property)和字段(field)沒有區分(如:命名規範、使用方式)的編程語言裏,區分屬性和字段只須要簡單的經過是否具備封裝性讀寫控制來區分。

屬性(property) 字段(field)
封裝性 具備封裝功能 不具備封裝功能
讀寫控制 能夠細分控制 不能細分控制

實體對象屬性校驗方式

嘻嘻,過兩天再說。~~~~

開源電商

Mallfoundry 是一個徹底開源的使用 Spring Boot 開發的多商戶電商平臺。它能夠嵌入到已有的 Java 程序中,或者做爲服務器、集羣、雲中的服務運行。

  • 領域模型採用領域驅動設計(DDD)、接口化以及面向對象設計。

項目地址:https://gitee.com/mallfoundry/mall

總結

屬性(property)與字段(field)有區別,總的來講是兩個方面:封裝性和訪問控制。

一個對象是由屬性和方法組成的,因此認識屬性時須要知道屬性是具備封裝性的。而不能只認識到只有方法須要封裝,屬性同樣須要封裝。、當屬性和方法都具備封裝性時,在使用具備屬性和方法的對象時纔不會極化。

相關文章
相關標籤/搜索