網站首頁 編程語言 正文
正文
不變性的概念非常簡單,在您創建結構體后,就永遠無法修改它。這個概念聽起來非常簡單,但您的程序想利用它從中收益并不是那么容易。接下來我們在 Go 中,使用不變性概念,來讓您的代碼更具有可讀性和穩定性。
減少對全局或外部狀態的依賴
當我們使用相同的參數,執行相同的函數兩次,我們的預期,應該得到相同的結果。但是當我們的函數中依賴外部狀態或全局變量時,函數可能會輸出不同的結果。我們最好避免這種情況。
函數的參數總是給定的,那我們調用,總是可以返回相同的函數。如果您有一個共享全局變量用于函數內部的某些內容,請考慮將該變量作為參數傳遞,而不是直接函數內部使用它。
這可以讓您的函數返回值更加可預測,并且更加易于測試,整個代碼的可讀性也會得到提高,因為調用者會知道,哪些值會影響函數的行為,參數的作用不就是會影響返回值的嗎?
讓我們看一個例子。
package main import ( "fmt" "math/rand" "time" ) var randNum int func main() { s1 := rand.NewSource(time.Now().UnixNano()) r1 := rand.New(s1) randNum = r1.Intn(100) fmt.Println(Add(1, 1)) } func Add(a, b int) int { return a + b + randNum }
Add
函數中使用了全局變量 randNum
作為計算的一部分,從函數簽名中并沒有體現這一點。更好的方法是,全局變量 randNum
應該作為參數傳遞,如下所示。
func Add(a, b, randNum int) int { return a + b + randNum }
這樣更具有可預測性,而且我們如果需要修改入參,影響的作用域也僅在 Add
函數中。
僅導出結構體的函數,而不是成員變量
我們知道,Go
結構體中的成員變量,如果首字母為大寫,那么該成員變量對外可見(這是編譯器決定的)。回到我們的博客,僅導出結構體函數,而不是成員變量,目的是希望成員變量的數據被保護,保證成員變量的有效的狀態!因為這可以讓您的代碼更加可靠,您不必維護每個修改該成員變量的操作,因為這些操作都將無效。
舉一個例子
ackage main import ( "fmt" ) type AK47 struct { bullet int } func NewAK47(bullet int) AK47 { return AK47{bullet: bullet} } func (a AK47) GetBullet() int { return a.bullet } func (a AK47) SetBullet(bullet int) { a.bullet = bullet } func main() { ak47 := NewAK47(30) fmt.Println(ak47.GetBullet()) ak47.SetBullet(20) fmt.Println(ak47.GetBullet()) }
我們定義了一個結構體 AK47
,這把槍有一個成員變量 bullet
子彈數,它是非導出字段,我們還定義了一個構造函數 NewAK47
和一個 GetBullet
函數。
一旦創建了 AK47
,就無法更改它的成員變量 bullet
了。此時您可能會有疑惑,如果我們需要修改成員變量呢?別急,您可以試試下面的方法。
在函數中使用復制值,而不是使用指針
在上一個副標題中,我們提到了一個概念,在創建結構體后永遠不要更改它。然而在實際中,我們經常需要修改結構體中的成員變量。
我們在使用不變性的同時,仍然可以維護實例化結構體的多個狀態,這并不意味著我們打破了結構體創建后不要更改它,我們更改的是它的副本,也就是復制后的結構體。復制后的結構體?難道我們需要去實現很多復制結構體每個字段的函數嗎?
當然不,我們可以利用 Go
的特性,在調用函數時,入參是復制值的行為。對于需要修改結構體中成員變量的操作,我們可以創建一個函數,該函數接收結構體為參數,并且返回一個修改后的結構體副本。
我們可以在不改變調用方結構體的情況下,修改該副本的任何內容,這意味著對于原結構體沒有任何副作用,并且該結構體的值仍然是可預測的。
不知道您有沒有用過 Go
標準庫的 Slice
切片,其中的 append
函數就使用了這個方法。讓我們接著用 AK47
來實現這個方法
代碼如下
package main import ( "fmt" ) type AK47 struct { bullet int } func NewAK47(bullet int) AK47 { return AK47{bullet: bullet} } func (a AK47) GetBullet() int { return a.bullet } func (a AK47) AddBullet(ak47 AK47) AK47 { newAK47 := NewAK47(a.GetBullet() + ak47.GetBullet()) return newAK47 } func main() { ak47 := NewAK47(30) add := NewAK47(20) fmt.Println(ak47.GetBullet()) ak47 = ak47.AddBullet(add) fmt.Println(ak47.GetBullet()) }
如您所見,我們通過 AddBullet
函數增加槍的子彈,但實際上并沒有更改傳入的結構體中的任何成員變量。最后,返回了一個帶有更新字段的新 AK47
結構體。
與復制值相比,指針更有優勢,尤其是當您的結構體成員變量、內容非常大時時,這種方法,通過復制的方式修改數據,可能會導致性能問題。您應該問自己,這么做是否值得,例如您正在編寫并發代碼?
總結
您在使用不變量時,請務必先權衡利弊。實現本篇博客中所描述的方法,需要大量的代碼。但是,如果我們在編寫并發代碼時,不考慮共享變量的不可變性,往往會出現與預期不符的情況,例如內存競態問題?其實我想說的就是線程安全問題 : - )
實現不變性,也可能出現嚴重的性能問題!這是一把雙刃劍。請不要過早的優化代碼。
原文鏈接:https://juejin.cn/post/7134621915766849573
相關推薦
- 2022-03-30 C#數據類型轉換(顯式轉型、隱式轉型、強制轉型)_C#教程
- 2022-08-16 C#?泛型List排序的實現_C#教程
- 2022-05-22 Nginx服務LNMP之WordPress部署流程步驟_nginx
- 2022-07-26 Python程序元素分析和注意事項
- 2023-10-25 對于Echarts實例化與銷毀的一些運用
- 2022-11-04 詳解Pytorch中的tensor數據結構_python
- 2022-02-12 OWASP列舉的Web應用程序十大安全漏洞 - SQL注入
- 2024-04-08 啟動spring-boot出現Error creating bean with name ‘conf
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支