ホームページ  >  記事  >  バックエンド開発  >  Goの不変型について詳しく解説

Goの不変型について詳しく解説

Guanhui
Guanhui転載
2020-06-15 18:01:564311ブラウズ

Goの不変型について詳しく解説

Golang における不変性

不変性を利用して Golang アプリケーションの可読性と安定性を向上させる方法

不変性の概念は非常に単純です。オブジェクト (または構造体) は一度作成されると、決して変更できません。不変です。概念は単純に見えますが、それを使用したり、その恩恵を受けるのはそれほど簡単ではありません。

コンピューター サイエンス (および人生) のほとんどのことと同様、同じ結果を達成する方法はたくさんあり、不変性の点では違いはありません。これはツールキット内のツールであり、適用可能なもので使用されるものと考える必要があります。問題のシナリオ。不変性の非常に良い使用例は、同時プログラミングを行っているときです。Golang は同時実行を念頭に置いて設計されているため、Go で同時実行を使用することは非常に一般的です。

どのパラダイムを使用するかにかかわらず、ここにいくつかありますGolang で不変性の概念を使用して、コードをより読みやすく安定させる方法。

構造体のフィールドをエクスポートせずに、構造体の機能のみをエクスポートします。

これは次のようなものです。カプセル化。エクスポートされていないフィールドを含む構造体を作成し、機能する関数のみをエクスポートします。これらの構造体の動作のみに関心があるため、この手法はインターフェイスに非常に役立ちます。この手法に追加するもう 1 つの良い方法は、構造体に関数 (またはコンストラクター) を作成します。こうすることで、構造体の状態が常に有効であることを保証できます。常に有効のままです。操作のたびに無効な状態を処理し続ける必要がないため、コードの信頼性が高まります。非常に基本的な例を示します:

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、コンストラクター ## を持ちます。 Amount タイプの #NewAmount および GetValue メソッド NewAmount 関数は Amount 構造を作成しますが、これは変更できません。したがって、パッケージの外部からは不変です (ただし、go 2 ではこれを変更する提案がありますが、go 1 では不変の構造を作成する方法はありません)。さらに、タイプ Amount の変数はありません。無効な状態 (この場合は負) です。これを作成する唯一の方法が既に検証済みであるため、別のパッケージから呼び出すことができます:

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

関数内のポインターの代わりに値のコピーを使用します

最も基本的な概念は、オブジェクト (または構造体) を作成し、それを再度変更しないことです。しかし、私たちはエンティティの状態が重要なアプリケーションに取り組むことがよくあります。ただし、エンティティの状態とプログラム内のエンティティの内部表現は異なります。不変性を使用する場合でも、エンティティに複数の状態を割り当てることができます。これは、作成された構造は変更されませんが、そのコピーは変更されることを意味します。これは、構造内の各フィールドをコピーする関数を手動で実装する必要があるという意味ではありません。

代わりに、関数を呼び出すときに値をコピーするという Go 言語のネイティブ動作に依存できます。エンティティの状態を変更する操作では、構造体をパラメータとして (または関数レシーバーとして) 受け取り、実行後に変更されたバージョンを返す関数を作成できます。これは、関数の呼び出し元によって引数として渡された変数を変更せずに、コピー上のあらゆるものを変更できるため、非常に強力な手法です。これは、副作用がなく、動作が予測可能であることを意味します。同じ構造体が並行関数に渡される場合、各構造体はその構造体へのポインターではなく、そのコピーを受け取ります。

スライス関数を使用している場合、この動作が

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

例に戻って、

Amount 型の
balance フィールドを含む Account 型を実装しましょう。同時に、Deposit メソッドと 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
}
作成したメソッドを調べると、関数の受信側である

Account 構造体の状態を実際に変更していることがわかります。ポインターを使用していないため、これは当てはまりません。また、構造体のコピーがこれらの関数のレシーバーとして渡されるため、関数のスコープ内でのみ有効なコピーを変更して返します。別のパッケージでこれを呼び出す例を次に示します。

a, err := amounts.NewAmount(10)
acc := accounts.NewEmptyAccount()
acc2 := acc.Deposit(a)
log.Println(acc.GetBalance())
log.Println(acc2.GetBalance())
コマンド ラインでの結果は次のようになります。

2020/06/03 22:22:40 {0}
2020/06/03 22:22:40 {10}
ご覧のとおり、変数

acc## を渡しているにもかかわらず、 #Deposit メソッドが呼び出されますが、変数は実際には変更されません。変更された内容を含む Account (acc2 に割り当てられている) の新しいコピーが返されます。分野。 <p>使用指针具有优于复制值的优点,特别是如果您的结构很大时,在复制时可能会导致性能问题,但是您应始终问自己是否值得,不要尝试过早地优化代码。尤其是在使用并发时。您可能会在一些糟糕的情况下结束。</p> <h2><span style="font-size: 16px;">减少全局或外部状态中的依赖性</span></h2> <p>不变性不仅可以应用于结构,还可以应用于函数。如果我们用相同的参数两次执行相同的函数,我们应该收到相同的结果,对吗?好吧,如果我们依赖于外部状态或全局变量,则可能并非总是如此。最好避免这种情况。有几种方法可以实现这一目标。</p> <p>如果您在函数内部使用共享的全局变量,请考虑将该值作为参数传递,而不是直接在函数内部使用。 那会使您的函数更可预测,也更易于测试。整个代码的可读性也会更容易,其他人也将会了解到值可能会影响函数行为,因为它是一个参数,而这就是参数的用途。 这里有一个例子:</p> <pre class="brush:php;toolbar:false">package main import (     &quot;fmt&quot;     &quot;time&quot; ) 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 }</pre> <p>这个函数 <code>sum 使用全局变量作为自己计算的一部分。 从函数签名来看这不是很清楚。 更好的方法是将rand变量作为参数传递。 因此该函数看起来应该像这样:

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

  推荐教程:《Go教程

以上がGoの不変型について詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。