목차
Golang의 불변성" >Golang의 불변성
在函数中使用值拷贝替代指针" >在函数中使用值拷贝替代指针
减少全局或外部状态中的依赖性" >减少全局或外部状态中的依赖性
백엔드 개발 Golang Go의 불변 유형에 대한 자세한 설명

Go의 불변 유형에 대한 자세한 설명

Jun 15, 2020 pm 06:01 PM
go golang

Go의 불변 유형에 대한 자세한 설명

Golang의 불변성

불변성을 사용하여 Golang 애플리케이션의 가독성과 안정성을 높이는 방법

불변성의 개념은 매우 간단합니다. 객체(또는 구조)를 만든 후에는 불가능합니다. 개념은 단순해 보이지만 이를 사용하거나 활용하기는 쉽지 않습니다.

컴퓨터 과학(및 생활)의 대부분과 마찬가지로 이를 수행하는 방법은 많습니다. 동일한 결과를 얻을 수 있으며 불변성 측면에서는 차이가 없습니다. 이를 툴킷의 도구로 생각하고 적용 가능한 문제 시나리오에 사용해야 합니다. 불변성에 대한 매우 좋은 사용 사례는 동시 프로그래밍을 수행할 때입니다.

어떤 패러다임을 사용하든 다음 방법을 사용하여 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
}
로그인 후 복사

이 패키지에서는 Amount를 정의합니다. 내보내지 않은 필드 value, 생성자 NewAmountAmount 유형에 대한 GetValue 메서드가 있는 유형입니다. 함수는 Amount 구조를 생성하므로 변경할 수 없습니다. 따라서 패키지 외부에서 변경할 수 없습니다(go 2에서는 이를 변경하라는 제안이 있지만 변경 불가능을 생성할 수 있는 방법은 없습니다). go 1)의 구조. 또한 잘못된 상태(이 경우 음수)인 Amount 유형의 변수가 없습니다. 이를 생성하는 유일한 방법이 확인되었기 때문입니다. 다른 패키지에서 가져왔습니다:

a, err := amounts.NewAmount(10)
*// 处理错误
*log.Println(a.GetValue())
로그인 후 복사

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

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
}
로그인 후 복사

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

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

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

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

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

a, err := amounts.NewAmount(10)
acc := accounts.NewEmptyAccount()
acc2 := acc.Deposit(a)
log.Println(acc.GetBalance())
log.Println(acc2.GetBalance())
로그인 후 복사

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

2020/06/03 22:22:40 {0}
2020/06/03 22:22:40 {10}
로그인 후 복사

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

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
}
로그인 후 복사
로그인 후 복사

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

🎜가장 기본적인 개념은 개체(또는 구조체 본문)를 만들고 다시는 변경하지 않는 것입니다. 그러나 우리는 엔터티 상태가 중요한 애플리케이션에 대해 작업하는 경우가 많습니다. 그러나 프로그램에서 엔터티 상태와 엔터티의 내부 표현은 다릅니다. 불변성을 사용할 때 엔터티에 여러 상태를 할당할 수 있습니다. 즉, 생성된 구조는 변경되지 않지만 복사본은 변경됩니다. 이는 구조의 각 필드를 복사하는 기능을 수동으로 구현해야 한다는 의미는 아닙니다. 🎜🎜대신 함수를 호출할 때 값을 복사하는 Go 언어의 기본 동작을 사용할 수 있습니다. 엔터티의 상태를 변경하는 작업의 경우 구조를 매개변수(또는 함수 수신자)로 수신하고 실행 후 변경된 버전을 반환하는 함수를 만들 수 있습니다. 함수 호출자가 인수로 전달한 변수를 변경하지 않고도 복사본에서 무엇이든 변경할 수 있기 때문에 이는 매우 강력한 기술입니다. 이는 부작용이 없고 예측 가능한 행동을 의미합니다. 동일한 구조가 동시 함수에 전달되면 각 구조는 해당 구조에 대한 포인터가 아닌 복사본을 받습니다. 🎜🎜슬라이싱 기능을 사용하면 [append](https://golang.org/pkg/builtin/#append) 기능에 이 동작이 적용되는 것을 볼 수 있습니다. 🎜🎜Back to us 이 예에서는
Amount 유형의 balance 필드가 포함된 Account 유형을 구현해 보겠습니다. 동시에 DepositWithdraw 메서드를 추가하여 Account 엔터티의 상태를 변경합니다. 🎜
func sum(a, b, rand **int**) **int** {
   return a + b + rand
}
로그인 후 복사
로그인 후 복사
🎜우리가 생성한 메서드를 살펴보면 함수의 수신자인 Account 구조의 상태를 실제로 변경하고 있는 것으로 보입니다. 포인터를 사용하지 않기 때문에 그렇지 않으며 구조체의 복사본이 이러한 함수의 수신자로 전달되므로 함수 범위 내에서만 유효한 복사본을 변경한 다음 반환합니다. 다음은 다른 패키지에서 이를 호출하는 예입니다. 🎜rrreee🎜명령줄의 결과는 다음과 같습니다. 🎜rrreee🎜보시다시피 Deposit은 <code>acc 변수를 통해 호출됩니다. 메서드를 사용했지만 변수가 실제로 변경되지 않은 경우 변경된 필드가 포함된 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
}
로그인 후 복사
로그인 후 복사

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

func sum(a, b, rand **int**) **int** {
   return a + b + rand
}
로그인 후 복사
로그인 후 복사

  推荐教程:《Go教程

위 내용은 Go의 불변 유형에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Golang을 사용하여 파일을 안전하게 읽고 쓰는 방법은 무엇입니까? Golang을 사용하여 파일을 안전하게 읽고 쓰는 방법은 무엇입니까? Jun 06, 2024 pm 05:14 PM

Go에서는 안전하게 파일을 읽고 쓰는 것이 중요합니다. 지침은 다음과 같습니다. 파일 권한 확인 지연을 사용하여 파일 닫기 파일 경로 유효성 검사 컨텍스트 시간 초과 사용 다음 지침을 따르면 데이터 보안과 애플리케이션의 견고성이 보장됩니다.

Golang 데이터베이스 연결을 위한 연결 풀을 구성하는 방법은 무엇입니까? Golang 데이터베이스 연결을 위한 연결 풀을 구성하는 방법은 무엇입니까? Jun 06, 2024 am 11:21 AM

Go 데이터베이스 연결을 위한 연결 풀링을 구성하는 방법은 무엇입니까? 데이터베이스 연결을 생성하려면 데이터베이스/sql 패키지의 DB 유형을 사용하고, 최대 동시 연결 수를 제어하려면 MaxIdleConns를 설정하고, 연결의 최대 수명 주기를 제어하려면 ConnMaxLifetime을 설정하세요.

JSON 데이터를 Golang의 데이터베이스에 저장하는 방법은 무엇입니까? JSON 데이터를 Golang의 데이터베이스에 저장하는 방법은 무엇입니까? Jun 06, 2024 am 11:24 AM

JSON 데이터는 gjson 라이브러리 또는 json.Unmarshal 함수를 사용하여 MySQL 데이터베이스에 저장할 수 있습니다. gjson 라이브러리는 JSON 필드를 구문 분석하는 편리한 방법을 제공하며, json.Unmarshal 함수에는 JSON 데이터를 비정렬화하기 위한 대상 유형 포인터가 필요합니다. 두 방법 모두 SQL 문을 준비하고 삽입 작업을 수행하여 데이터를 데이터베이스에 유지해야 합니다.

Golang 프레임워크 vs. Go 프레임워크: 내부 아키텍처와 외부 기능 비교 Golang 프레임워크 vs. Go 프레임워크: 내부 아키텍처와 외부 기능 비교 Jun 06, 2024 pm 12:37 PM

GoLang 프레임워크와 Go 프레임워크의 차이점은 내부 아키텍처와 외부 기능에 반영됩니다. GoLang 프레임워크는 Go 표준 라이브러리를 기반으로 하며 기능을 확장하는 반면, Go 프레임워크는 특정 목적을 달성하기 위해 독립적인 라이브러리로 구성됩니다. GoLang 프레임워크는 더 유연하고 Go 프레임워크는 사용하기 더 쉽습니다. GoLang 프레임워크는 성능 면에서 약간의 이점이 있고 Go 프레임워크는 확장성이 더 좋습니다. 사례: gin-gonic(Go 프레임워크)은 REST API를 구축하는 데 사용되고 Echo(GoLang 프레임워크)는 웹 애플리케이션을 구축하는 데 사용됩니다.

프론트 엔드에서 백엔드 개발로 전환하면 Java 또는 Golang을 배우는 것이 더 유망합니까? 프론트 엔드에서 백엔드 개발로 전환하면 Java 또는 Golang을 배우는 것이 더 유망합니까? Apr 02, 2025 am 09:12 AM

백엔드 학습 경로 : 프론트 엔드에서 백엔드 초보자로서 프론트 엔드에서 백엔드까지의 탐사 여행은 프론트 엔드 개발에서 변화하는 백엔드 초보자로서 이미 Nodejs의 기초를 가지고 있습니다.

Golang 정규 표현식과 일치하는 첫 번째 하위 문자열을 찾는 방법은 무엇입니까? Golang 정규 표현식과 일치하는 첫 번째 하위 문자열을 찾는 방법은 무엇입니까? Jun 06, 2024 am 10:51 AM

FindStringSubmatch 함수는 정규 표현식과 일치하는 첫 번째 하위 문자열을 찾습니다. 이 함수는 일치하는 하위 문자열이 포함된 조각을 반환합니다. 첫 번째 요소는 전체 일치 문자열이고 후속 요소는 개별 하위 문자열입니다. 코드 예: regexp.FindStringSubmatch(text,pattern)는 일치하는 하위 문자열의 조각을 반환합니다. 실제 사례: 이메일 주소의 도메인 이름을 일치시키는 데 사용할 수 있습니다. 예를 들어 이메일:="user@example.com", 패턴:=@([^\s]+)$를 사용하여 도메인 이름 일치를 가져옵니다. [1].

Golang 프레임워크 개발 실습 튜토리얼: FAQ Golang 프레임워크 개발 실습 튜토리얼: FAQ Jun 06, 2024 am 11:02 AM

Go 프레임워크 개발 FAQ: 프레임워크 선택: Gin(API), Echo(확장 가능), Beego(ORM), Iris(성능) 등 애플리케이션 요구 사항 및 개발자 선호도에 따라 다릅니다. 설치 및 사용: gomod 명령을 사용하여 프레임워크를 설치하고 가져와서 사용합니다. 데이터베이스 상호 작용: gorm과 같은 ORM 라이브러리를 사용하여 데이터베이스 연결 및 작업을 설정합니다. 인증 및 권한 부여: gin-contrib/sessions와 같은 세션 관리 및 인증 미들웨어를 사용합니다. 실제 사례: Gin 프레임워크를 사용하여 POST, GET 및 기타 기능을 제공하는 간단한 블로그 API를 구축합니다.

Golang에서 미리 정의된 시간대를 사용하는 방법은 무엇입니까? Golang에서 미리 정의된 시간대를 사용하는 방법은 무엇입니까? Jun 06, 2024 pm 01:02 PM

Go에서 미리 정의된 시간대를 사용하는 단계는 다음과 같습니다. "time" 패키지를 가져옵니다. LoadLocation 함수를 통해 특정 시간대를 로드합니다. Time 객체 생성, 시간 문자열 구문 분석, 날짜 및 시간 변환 수행 등의 작업에 로드된 시간대를 사용합니다. 미리 정의된 시간대 기능의 적용을 설명하기 위해 다양한 시간대를 사용하여 날짜를 비교합니다.

See all articles