Jadual Kandungan
Golang 中的不变性" >Golang 中的不变性
仅导出结构体的功能, 而不导出其字段" >仅导出结构体的功能, 而不导出其字段
在函数中使用值拷贝替代指针" >在函数中使用值拷贝替代指针
减少全局或外部状态中的依赖性" >减少全局或外部状态中的依赖性
Rumah pembangunan bahagian belakang Golang 详解 Go 中的不可变类型

详解 Go 中的不可变类型

Jun 15, 2020 pm 06:01 PM
go golang

详解 Go 中的不可变类型

Golang 中的不变性

如何利用不变性来增强你的 Golang 应用程序的可读性和稳定性

不变性的概念非常简单. 创建对象 (或结构体) 后, 将永远无法更改它. 这是一成不变的. 尽管这个概念看起来很简单, 但使用它或从中受益并不那么容易.

正如计算机科学 (和生活) 中的大多数事物一样, 有许多种方法可以达到相同的结果, 就不变性而言, 两者没有什么不同. 您应该把它看做是工具包中的一个工具, 并使用在适用的问题场景上. 关于不变性的一个非常好的用例是在您进行并发编程时. Golang 在设计时就考虑了并发性, 因此在 go 中使用并发非常普遍. 

无论您使用哪种范例都可以通过以下方法在 Golang 中使用一些不变性概念来使代码更具可读性和稳定性.

仅导出结构体的功能, 而不导出其字段

这与封装类似. 使用非导出字段创建结构, 仅导出作用的函数. 由于您只对那些结构的行为感兴趣, 因此该技术对接口非常有用. 这项技术的另一个很好的补充是将创建函数 (或构造函数) 添加并导出到您的结构中. 这样您可以确保该结构的状态始终有效. 始终保持有效状态可以使代码更加可靠, 因为您不必继续处理要对该结构进行的每个操作的无效状态. 下面是一个非常基本的示例:

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
}
Salin selepas log masuk

在此程序包中, 我们定义了 Amount 类型, 具有未导出的字段 value, 构造函数 NewAmount以及 GetValue 方法用于 Amount类型. 一旦 NewAmount 函数创建了 Amount 结构, 就无法更改它. 因此它从包的外部来说是不可变的 (尽管在 go 2 中有 更改此内容的建议, 但 go 1 中没有创建不变结构的方法). 此外没有处于无效状态 (在这种情况下为负数) 的 Amount 类型的变量, 因为创建它们的唯一方法已经对此进行了验证. 我们可以从另一个包中调用它:

a, err := amounts.NewAmount(10)
*// 处理错误
*log.Println(a.GetValue())
Salin selepas log masuk

在函数中使用值拷贝替代指针

最基本的概念是在创建一个对象(或者结构体)后,再也不去改变它。但是我们经常在实体状态很重要的应用上工作。不过,程序中实体状态和实体内部表示是不同的。在使用不变性时,我们仍然可以给实体赋予多个状态。这意味着已创建的结构体不会改变,但是它的副本会改变。这并不意味着我们需要手动实现复制结构体中每个字段的功能。

相反地,当调用函数时我们可以依赖 Go 语言复制值的本机行为。对于任意一个会改变实体状态的操作,我们可以创建一个用来接收结构体作为参数(或者作为函数接收器)的函数,在执行完毕之后返回改变后的版本。这是一项非常强大的技术,因为你能够改变副本上的任何内容,而无需更改函数调用者作为参数传递的变量。这意味着没有副作用和可预测的行为。如果相同的结构体被传递给并发函数,每个结构体都会接收到它的副本,而不是指向它的指针。

当你在使用切片功能时,你会看到此行为应用于 [append](https://golang.org/pkg/builtin/#append) 函数

回到我们的例子中,让我们实现 Account 类型,它包含了
Amount 类型的 balance 字段。同时,我们添加 DepositWithdraw 方法来改变 Account 实体的状态。

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
}
Salin selepas log masuk

如果你检查我们创建的方法,他们会觉得我们事实上改变了作为函数接收器的 Account 结构的状态。由于我们没有使用指针,情况并非如此,由于结构体的副本作为这些函数的接收器来传递,我们将更改只在函数作用域内有效的副本,然后返回它。这是在另一个包中调用它的示例:

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

命令行上的结果会是这样的:

2020/06/03 22:22:40 {0}
2020/06/03 22:22:40 {10}
Salin selepas log masuk

如你所见,尽管通过变量 acc 调用了 Deposit 方法,但实际上变量并没有改变,它返回了新的  Account 副本(分配给 acc2),其包含了改变后的字段。

使用指针具有优于复制值的优点,特别是如果您的结构很大时,在复制时可能会导致性能问题,但是您应始终问自己是否值得,不要尝试过早地优化代码。尤其是在使用并发时。您可能会在一些糟糕的情况下结束。

减少全局或外部状态中的依赖性

不变性不仅可以应用于结构,还可以应用于函数。如果我们用相同的参数两次执行相同的函数,我们应该收到相同的结果,对吗?好吧,如果我们依赖于外部状态或全局变量,则可能并非总是如此。最好避免这种情况。有几种方法可以实现这一目标。

如果您在函数内部使用共享的全局变量,请考虑将该值作为参数传递,而不是直接在函数内部使用。 那会使您的函数更可预测,也更易于测试。整个代码的可读性也会更容易,其他人也将会了解到值可能会影响函数行为,因为它是一个参数,而这就是参数的用途。 这里有一个例子:

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
}
Salin selepas log masuk

这个函数 sum 使用全局变量作为自己计算的一部分。 从函数签名来看这不是很清楚。 更好的方法是将rand变量作为参数传递。 因此该函数看起来应该像这样:

func sum(a, b, rand **int**) **int** {
   return a + b + rand
}
Salin selepas log masuk

  推荐教程:《Go教程

Atas ialah kandungan terperinci 详解 Go 中的不可变类型. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

AI Hentai Generator

AI Hentai Generator

Menjana ai hentai secara percuma.

Artikel Panas

R.E.P.O. Kristal tenaga dijelaskan dan apa yang mereka lakukan (kristal kuning)
2 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Tetapan grafik terbaik
2 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Cara Memperbaiki Audio Jika anda tidak dapat mendengar sesiapa
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Bagaimana untuk membaca dan menulis fail dengan selamat menggunakan Golang? Bagaimana untuk membaca dan menulis fail dengan selamat menggunakan Golang? Jun 06, 2024 pm 05:14 PM

Membaca dan menulis fail dengan selamat dalam Go adalah penting. Garis panduan termasuk: Menyemak kebenaran fail Menutup fail menggunakan tangguh Mengesahkan laluan fail Menggunakan tamat masa konteks Mengikuti garis panduan ini memastikan keselamatan data anda dan keteguhan aplikasi anda.

Bagaimana untuk mengkonfigurasi kolam sambungan untuk sambungan pangkalan data Golang? Bagaimana untuk mengkonfigurasi kolam sambungan untuk sambungan pangkalan data Golang? Jun 06, 2024 am 11:21 AM

Bagaimana untuk mengkonfigurasi pengumpulan sambungan untuk sambungan pangkalan data Go? Gunakan jenis DB dalam pakej pangkalan data/sql untuk membuat sambungan pangkalan data untuk mengawal bilangan maksimum sambungan serentak;

Perbandingan kebaikan dan keburukan rangka kerja golang Perbandingan kebaikan dan keburukan rangka kerja golang Jun 05, 2024 pm 09:32 PM

Rangka kerja Go menyerlah kerana kelebihan prestasi tinggi dan konkurensinya, tetapi ia juga mempunyai beberapa kelemahan, seperti agak baharu, mempunyai ekosistem pembangun yang kecil dan kekurangan beberapa ciri. Selain itu, perubahan pantas dan keluk pembelajaran boleh berbeza dari rangka kerja ke rangka kerja. Rangka kerja Gin ialah pilihan popular untuk membina API RESTful kerana penghalaan yang cekap, sokongan JSON terbina dalam dan pengendalian ralat yang berkuasa.

Bagaimana untuk menggunakan gomega untuk penegasan dalam ujian unit Golang? Bagaimana untuk menggunakan gomega untuk penegasan dalam ujian unit Golang? Jun 05, 2024 pm 10:48 PM

Cara menggunakan Gomega untuk penegasan dalam ujian unit Golang Dalam ujian unit Golang, Gomega ialah perpustakaan penegasan yang popular dan berkuasa yang menyediakan kaedah penegasan yang kaya supaya pembangun boleh mengesahkan keputusan ujian dengan mudah. Pasang Gomegagoget-ugithub.com/onsi/gomega Menggunakan Gomega untuk penegasan Berikut ialah beberapa contoh biasa menggunakan Gomega untuk penegasan: 1. Import penegasan kesamaan "github.com/onsi/gomega" funcTest_MyFunction(t*testing.T){

Apakah amalan terbaik untuk pengendalian ralat dalam rangka kerja Golang? Apakah amalan terbaik untuk pengendalian ralat dalam rangka kerja Golang? Jun 05, 2024 pm 10:39 PM

Amalan terbaik: Cipta ralat tersuai menggunakan jenis ralat yang ditakrifkan dengan baik (pakej ralat) Sediakan lebih banyak butiran Log ralat dengan sewajarnya Sebarkan ralat dengan betul dan elakkan menyembunyikan atau menyekat ralat Balut seperti yang diperlukan untuk menambah konteks

Bagaimana untuk menyimpan data JSON ke pangkalan data di Golang? Bagaimana untuk menyimpan data JSON ke pangkalan data di Golang? Jun 06, 2024 am 11:24 AM

Data JSON boleh disimpan ke dalam pangkalan data MySQL dengan menggunakan perpustakaan gjson atau fungsi json.Unmarshal. Pustaka gjson menyediakan kaedah kemudahan untuk menghuraikan medan JSON dan fungsi json.Unmarshal memerlukan penuding jenis sasaran kepada data JSON unmarshal. Kedua-dua kaedah memerlukan penyediaan pernyataan SQL dan melaksanakan operasi sisipan untuk mengekalkan data ke dalam pangkalan data.

Rangka Kerja Golang lwn Rangka Kerja Go: Perbandingan Seni Bina Dalaman dan Ciri Luaran Rangka Kerja Golang lwn Rangka Kerja Go: Perbandingan Seni Bina Dalaman dan Ciri Luaran Jun 06, 2024 pm 12:37 PM

Perbezaan antara rangka kerja GoLang dan rangka kerja Go ditunjukkan dalam seni bina dalaman dan ciri luaran. Rangka kerja GoLang adalah berdasarkan perpustakaan standard Go dan meluaskan fungsinya, manakala rangka kerja Go terdiri daripada perpustakaan bebas untuk mencapai tujuan tertentu. Rangka kerja GoLang lebih fleksibel dan rangka kerja Go lebih mudah digunakan. Rangka kerja GoLang mempunyai sedikit kelebihan dalam prestasi dan rangka kerja Go lebih berskala. Kes: gin-gonic (rangka Go) digunakan untuk membina REST API, manakala Echo (rangka kerja GoLang) digunakan untuk membina aplikasi web.

Bagaimana untuk menyelesaikan masalah keselamatan biasa dalam rangka kerja golang? Bagaimana untuk menyelesaikan masalah keselamatan biasa dalam rangka kerja golang? Jun 05, 2024 pm 10:38 PM

Cara menangani isu keselamatan biasa dalam rangka kerja Go Dengan penggunaan meluas rangka kerja Go dalam pembangunan web, memastikan keselamatannya adalah penting. Berikut ialah panduan praktikal untuk menyelesaikan masalah keselamatan biasa, dengan kod sampel: 1. SQL Injection Gunakan pernyataan yang disediakan atau pertanyaan berparameter untuk mengelakkan serangan suntikan SQL. Contohnya: constquery="SELECT*FROMusersWHEREusername=?"stmt,err:=db.Prepare(query)iferr!=nil{//Handleerror}err=stmt.QueryR

See all articles