溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

golang中的不可變類型有哪些

發(fā)布時間:2020-06-16 10:33:50 來源:億速云 閱讀:238 作者:Leah 欄目:編程語言

golang中的不可變類型有哪些?可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

Golang 中的不變性

如何利用不變性來增強你的 Golang 應用程序的可讀性和穩(wěn)定性

不變性的概念非常簡單. 創(chuàng)建對象 (或結(jié)構體) 后, 將永遠無法更改它. 這是一成不變的. 盡管這個概念看起來很簡單, 但使用它或從中受益并不那么容易.

正如計算機科學 (和生活) 中的大多數(shù)事物一樣, 有許多種方法可以達到相同的結(jié)果, 就不變性而言, 兩者沒有什么不同. 您應該把它看做是工具包中的一個工具, 并使用在適用的問題場景上. 關于不變性的一個非常好的用例是在您進行并發(fā)編程時. Golang 在設計時就考慮了并發(fā)性, 因此在 go 中使用并發(fā)非常普遍.

無論您使用哪種范例都可以通過以下方法在 Golang 中使用一些不變性概念來使代碼更具可讀性和穩(wěn)定性.

僅導出結(jié)構體的功能, 而不導出其字段

這與封裝類似. 使用非導出字段創(chuàng)建結(jié)構, 僅導出作用的函數(shù). 由于您只對那些結(jié)構的行為感興趣, 因此該技術對接口非常有用. 這項技術的另一個很好的補充是將創(chuàng)建函數(shù) (或構造函數(shù)) 添加并導出到您的結(jié)構中. 這樣您可以確保該結(jié)構的狀態(tài)始終有效. 始終保持有效狀態(tài)可以使代碼更加可靠, 因為您不必繼續(xù)處理要對該結(jié)構進行的每個操作的無效狀態(tài). 下面是一個非?;镜氖纠?

package amounts

import "errors"

type Amount struct {
    value int
}

func NewAmount(value int) (Amount, error) {
    if value < 0 {
        return Amount{}, errors.New("Invalid amount")
    }

    return Amount{value: value}, nil
}

func (a Amount) GetValue() int {
    return a.value
}

在此程序包中, 我們定義了 Amount 類型, 具有未導出的字段 value, 構造函數(shù) NewAmount以及 GetValue 方法用于 Amount類型. 一旦 NewAmount 函數(shù)創(chuàng)建了 Amount 結(jié)構, 就無法更改它. 因此它從包的外部來說是不可變的 (盡管在 go 2 中有 更改此內(nèi)容的建議, 但 go 1 中沒有創(chuàng)建不變結(jié)構的方法). 此外沒有處于無效狀態(tài) (在這種情況下為負數(shù)) 的 Amount 類型的變量, 因為創(chuàng)建它們的唯一方法已經(jīng)對此進行了驗證. 我們可以從另一個包中調(diào)用它:

a, err := amounts.NewAmount(10)
*// 處理錯誤
*log.Println(a.GetValue())

在函數(shù)中使用值拷貝替代指針

最基本的概念是在創(chuàng)建一個對象(或者結(jié)構體)后,再也不去改變它。但是我們經(jīng)常在實體狀態(tài)很重要的應用上工作。不過,程序中實體狀態(tài)和實體內(nèi)部表示是不同的。在使用不變性時,我們?nèi)匀豢梢越o實體賦予多個狀態(tài)。這意味著已創(chuàng)建的結(jié)構體不會改變,但是它的副本會改變。這并不意味著我們需要手動實現(xiàn)復制結(jié)構體中每個字段的功能。

相反地,當調(diào)用函數(shù)時我們可以依賴 Go 語言復制值的本機行為。對于任意一個會改變實體狀態(tài)的操作,我們可以創(chuàng)建一個用來接收結(jié)構體作為參數(shù)(或者作為函數(shù)接收器)的函數(shù),在執(zhí)行完畢之后返回改變后的版本。這是一項非常強大的技術,因為你能夠改變副本上的任何內(nèi)容,而無需更改函數(shù)調(diào)用者作為參數(shù)傳遞的變量。這意味著沒有副作用和可預測的行為。如果相同的結(jié)構體被傳遞給并發(fā)函數(shù),每個結(jié)構體都會接收到它的副本,而不是指向它的指針。

當你在使用切片功能時,你會看到此行為應用于 [append](https://golang.org/pkg/builtin/#append) 函數(shù)

回到我們的例子中,讓我們實現(xiàn) Account 類型,它包含了
Amount 類型的 balance 字段。同時,我們添加 DepositWithdraw 方法來改變 Account 實體的狀態(tài)。

package accounts

import (
    "errors"
    "my-package/amounts"
)

type Account struct {
    balance amounts.Amount
}

func NewEmptyAccount() Account {
    amount, _ := amounts.NewAmount(0)
    return NewAccount(amount)
}

func NewAccount(amount amounts.Amount) Account {
    return Account{balance: amount}
}

func (acc Account) Deposit(amount amounts.Amount) Account {
    newAmount, _ := amounts.NewAmount(acc.balance.GetValue() + amount.GetValue())
    acc.balance = newAmount
    return acc
}

func (acc Account) Withdraw(amount amounts.Amount) (Account, error) {
    newAmount, err := amounts.NewAmount(acc.balance.GetValue() - amount.GetValue())
    if err != nil {
        return acc, errors.New("Insuficient funds")
    }
    acc.balance = newAmount
    return acc, nil
}

如果你檢查我們創(chuàng)建的方法,他們會覺得我們事實上改變了作為函數(shù)接收器的 Account 結(jié)構的狀態(tài)。由于我們沒有使用指針,情況并非如此,由于結(jié)構體的副本作為這些函數(shù)的接收器來傳遞,我們將更改只在函數(shù)作用域內(nèi)有效的副本,然后返回它。這是在另一個包中調(diào)用它的示例:

a, err := amounts.NewAmount(10)
acc := accounts.NewEmptyAccount()
acc2 := acc.Deposit(a)
log.Println(acc.GetBalance())
log.Println(acc2.GetBalance())

命令行上的結(jié)果會是這樣的:

2020/06/03 22:22:40 {0}
2020/06/03 22:22:40 {10}

如你所見,盡管通過變量 acc 調(diào)用了 Deposit 方法,但實際上變量并沒有改變,它返回了新的  Account 副本(分配給 acc2),其包含了改變后的字段。

使用指針具有優(yōu)于復制值的優(yōu)點,特別是如果您的結(jié)構很大時,在復制時可能會導致性能問題,但是您應始終問自己是否值得,不要嘗試過早地優(yōu)化代碼。尤其是在使用并發(fā)時。您可能會在一些糟糕的情況下結(jié)束。

減少全局或外部狀態(tài)中的依賴性

不變性不僅可以應用于結(jié)構,還可以應用于函數(shù)。如果我們用相同的參數(shù)兩次執(zhí)行相同的函數(shù),我們應該收到相同的結(jié)果,對嗎?好吧,如果我們依賴于外部狀態(tài)或全局變量,則可能并非總是如此。最好避免這種情況。有幾種方法可以實現(xiàn)這一目標。

如果您在函數(shù)內(nèi)部使用共享的全局變量,請考慮將該值作為參數(shù)傳遞,而不是直接在函數(shù)內(nèi)部使用。 那會使您的函數(shù)更可預測,也更易于測試。整個代碼的可讀性也會更容易,其他人也將會了解到值可能會影響函數(shù)行為,因為它是一個參數(shù),而這就是參數(shù)的用途。 這里有一個例子:

package main

import (
    "fmt"
    "time"
)

var rand int = 0

func main() {
    rand = time.Now().Second() + 1
    fmt.Println(sum(1, 2))
}

func sum(a, b int) int {
    return a + b + rand
}

這個函數(shù) sum 使用全局變量作為自己計算的一部分。 從函數(shù)簽名來看這不是很清楚。 更好的方法是將rand變量作為參數(shù)傳遞。 因此該函數(shù)看起來應該像這樣:

func sum(a, b, rand **int**) **int** {
   return a + b + rand
}

看完上述內(nèi)容,你們對golang中的不可變類型有進一步的了解嗎?如果還想了解更多相關內(nèi)容,歡迎關注億速云行業(yè)資訊頻道,感謝各位的閱讀。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI