Maison  >  Article  >  développement back-end  >  Explication détaillée des types immuables dans Go

Explication détaillée des types immuables dans Go

Guanhui
Guanhuiavant
2020-06-15 18:01:564183parcourir

Explication détaillée des types immuables dans Go

Immuabilité dans Golang

Comment tirer parti de l'immuabilité pour améliorer la lisibilité et la stabilité de vos applications Golang

Le concept d'immuabilité est très simple. Une fois qu'un objet (ou une structure) est créé, il ne peut jamais être modifié. Bien que le concept semble simple, l'utiliser ou en bénéficier n'est pas si facile.

Comme pour la plupart des choses en informatique (et dans la vie), il existe de nombreuses façons d'obtenir le même résultat, et en termes d'invariance, il n'y a pas de différence. Vous devriez y penser comme un outil dans la boîte à outils et utilisé. scénarios de problèmes applicables. Un très bon cas d'utilisation de l'immuabilité est lorsque vous effectuez une programmation simultanée. Golang a été conçu en pensant à la concurrence, donc utiliser la concurrence dans go est très courant

Quel que soit le paradigme que vous utilisez, il existe des moyens. pour utiliser certains concepts d'immuabilité dans Golang pour rendre votre code plus lisible et stable

Exportez uniquement la fonctionnalité d'une structure sans exporter ses champs

Ceci est similaire à l'encapsulation. . Créez une structure avec des champs non exportés et exportez uniquement les fonctions qui agissent. Puisque vous n'êtes intéressé que par le comportement de ces structures, cette technique est très utile pour les interfaces. Un autre bon ajout à cette technique est d'ajouter et d'exporter une création. fonction (ou constructeur) à votre structure. De cette façon, vous pouvez vous assurer que l'état de la structure est toujours valide. Peut rendre le code plus fiable car vous n'avez pas à gérer un état invalide pour chaque opération. que vous voulez faire avec la structure. Voici un exemple très basique :

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
}

Dans ce package, nous définissons le Amount type, avec les champs non exportés value, le constructeur NewAmount et les méthodes GetValue pour le type Amount. Une fois que la fonction NewAmount crée la structure Amount, elle ne peut pas être modifiée. Par conséquent, elle est supprimée du package et est immuable en externe (il n'y a aucun moyen de créer des structures immuables dans go 1, bien qu'il y en ait. suggestions pour changer cela dans go 2). De plus, il n'y a pas de , puisque la seule façon de les créer a été vérifiée. Nous pouvons l'appeler depuis un autre package : Amount

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

Utiliser. des copies de valeurs au lieu de pointeurs dans les fonctions

Le concept le plus basique est de ne jamais modifier un objet (ou une structure) après l'avoir créé. Mais nous travaillons souvent sur des applications où l’état de l’entité est important. Cependant, l'état de l'entité et la représentation interne de l'entité dans le programme sont différents. Lorsque nous utilisons l'immuabilité, nous pouvons toujours attribuer plusieurs états aux entités. Cela signifie que la structure créée ne changera pas, mais sa copie changera. Cela ne signifie pas que nous devons implémenter manuellement la fonction de copie de chaque champ de la structure.

Au lieu de cela, nous pouvons nous appuyer sur le comportement natif du langage Go consistant à copier des valeurs lors de l'appel de fonctions. Pour toute opération qui modifie l'état d'une entité, nous pouvons créer une fonction qui reçoit une structure en paramètre (ou en tant que récepteur de fonction) et renvoie la version modifiée après exécution. Il s'agit d'une technique très puissante car vous pouvez modifier n'importe quoi sur la copie sans modifier les variables passées en arguments par l'appelant de la fonction. Cela signifie aucun effet secondaire et un comportement prévisible. Si la même structure est transmise à des fonctions concurrentes, chaque structure en reçoit une copie plutôt qu'un pointeur vers elle.

Lorsque vous utilisez la fonction slicing, vous verrez que ce comportement est appliqué à la fonction

[append](https://golang.org/pkg/builtin/#append)

Retour à notre exemple, implémentons le type

, qui contient Account
champ de type Amount. En même temps, nous ajoutons les méthodes balance et Deposit pour changer l'état de l'entité Withdraw. 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
}
Si vous inspectez les méthodes que nous avons créées, elles semblent que nous modifions réellement l'état de la

structure qui est le récepteur de la fonction. Puisque nous n'utilisons pas de pointeurs, ce n'est pas le cas, et puisqu'une copie de la structure est passée en tant que récepteur de ces fonctions, nous modifierons la copie qui n'est valide que dans la portée de la fonction, puis la renverrons. Voici un exemple d'appel dans un autre package : Account

a, err := amounts.NewAmount(10)
acc := accounts.NewEmptyAccount()
acc2 := acc.Deposit(a)
log.Println(acc.GetBalance())
log.Println(acc2.GetBalance())
Le résultat sur la ligne de commande serait :

2020/06/03 22:22:40 {0}
2020/06/03 22:22:40 {10}
Comme vous pouvez le voir, même si

a été appelé via la variable acc 🎜>, mais la variable n'est pas réellement modifiée, elle renvoie une nouvelle copie de Deposit (attribuée à Account), qui contient les champs modifiés. 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教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer