Goでインターフェイスを使用する方法

前書き

汎用性の高いプログラムを開発するには、柔軟で再利用可能なモジュール式のコードを書くことが不可欠です。 この方法で作業すると、複数の場所で同じ変更を行う必要がなくなるため、コードの保守が容易になります。 これを達成する方法は、言語によって異なります。 たとえば、https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)[inheritance]は、Java、C ++、C#などの言語で使用される一般的なアプローチです。

開発者はhttps://en.wikipedia.org/wiki/Object_composition[composition]から同じ設計目標を達成することもできます。 コンポジションは、オブジェクトまたはデータ型をより複雑なものに結合する方法です。 これは、Goがコードの再利用、モジュール性、および柔軟性を促進するために使用するアプローチです。 Goのインターフェイスは、複雑なコンポジションを整理する方法を提供し、それらの使用方法を学習することで、共通の再利用可能なコードを作成できます。

この記事では、一般的な動作を持つカスタムタイプを作成する方法を学習します。これにより、コードを再利用できます。 また、別のパッケージで定義されたインターフェイスを満たす独自のカスタムタイプのインターフェイスを実装する方法についても学習します。

動作の定義

コンポジションのコア実装の1つは、インターフェイスの使用です。 インターフェイスは、型の動作を定義します。 Go標準ライブラリで最も一般的に使用されるインターフェイスの1つはhttps://golang.org/pkg/fmt/#Stringer [+ fmt.Stringer +]インターフェイスです。

type Stringer interface {
   String() string
}

コードの最初の行は、「+ Stringer 」と呼ばれる「 type 」を定義します。 次に、「 interface」であることを示します。 構造体を定義するのと同じように、Goは中かっこ( + {} +)を使用してインターフェイスの定義を囲みます。 構造体の定義と比較して、インターフェイスの_behavior_のみを定義します。つまり、「このタイプは何ができるか」です。

`+ Stringer `インターフェースの場合、唯一の動作は ` String()+`メソッドです。 メソッドは引数を取らず、文字列を返します。

次に、 `+ fmt.Stringer +`動作を持つコードを見てみましょう。

main.go

package main

import "fmt"

type Article struct {
   Title string
   Author string
}

func (a Article) String() string {
   return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}

func main() {
   a := Article{
       Title: "Understanding Interfaces in Go",
       Author: "Sammy Shark",
   }
   fmt.Println(a.String())
}

最初に行うことは、 `+ Article `という新しいタイプを作成することです。 このタイプには、「 Title 」フィールドと「 Author +」フィールドがあり、両方ともhttps://www.digitalocean.com/community/tutorials/understanding-data-types-in-go[data type]という文字列です。

main.go

...
type Article struct {
   Title string
   Author string
}
...

次に、 + Article +`タイプで `+ String +`と呼ばれるhttps://www.digitalocean.com/community/tutorials/defining-methods-in-go [+ method `]を定義します。 ` String `メソッドは、 ` Article +`タイプを表す文字列を返します。

main.go

...
func (a Article) String() string {
   return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}
...

次に、 + main + functionで、 `+ Article のインスタンスを作成します`https://www.digitalocean.com/community/tutorials/how-to-use-variables-and-constants-in-go [変数]と呼ばれる「 a 」と入力して割り当てます。 ` Title `フィールドには `" Goのインターフェースについて "`の値を、 ` Author `フィールドには `" Sammy Shark "+`の値を提供します。

main.go

...
a := Article{
   Title: "Understanding Interfaces in Go",
   Author: "Sammy Shark",
}
...

次に、 + fmt.Println`を呼び出し、 + a.String()+ メソッド呼び出しの結果を渡すことで、 + String`メソッドの結果を出力します。

main.go

...
fmt.Println(a.String())

プログラムを実行すると、次の出力が表示されます。

OutputThe "Understanding Interfaces in Go" article was written by Sammy Shark.

これまで、インターフェイスを使用していませんでしたが、動作を持つタイプを作成しました。 この動作は、 `+ fmt.Stringer +`インターフェースと一致しました。 次に、その動作を使用してコードを再利用可能にする方法を見てみましょう。

インターフェイスの定義

目的の動作で定義された型ができたので、その動作の使用方法を見てみましょう。

ただし、その前に、関数の `+ Article `タイプから ` String +`メソッドを呼び出す場合に必要なことを見てみましょう。

main.go

package main

import "fmt"

type Article struct {
   Title string
   Author string
}

func (a Article) String() string {
   return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}

func main() {
   a := Article{
       Title: "Understanding Interfaces in Go",
       Author: "Sammy Shark",
   }

}

このコードでは、引数として + Article +`をとる `+ Print +`という新しい関数を追加します。 `+ Print`関数が行う唯一のことは + String`メソッドを呼び出すことです。 このため、代わりに関数に渡すインターフェイスを定義できます。

main.go

package main

import "fmt"

type Article struct {
   Title string
   Author string
}

func (a Article) String() string {
   return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}





func main() {
   a := Article{
       Title: "Understanding Interfaces in Go",
       Author: "Sammy Shark",
   }
   Print(a)
}

func Print() {
   fmt.Println()
}

ここで、 `+ Stringer +`というインターフェイスを作成しました:

main.go

...
type Stringer interface {
   String() string
}
...

`+ Stringer `インターフェースには、 ` string `を返す ` String()+`と呼ばれるメソッドが1つしかありません。 methodは、Goの特定の型に範囲が限定された特別な関数です。 関数とは異なり、メソッドは定義された型のインスタンスからのみ呼び出すことができます。

次に、 `+ Print `メソッドのシグネチャを更新して、 ` Article `の具体的なタイプではなく、 ` Stringer `を取得します。 コンパイラは ` Stringer `インターフェースが ` String `メソッドを定義することを知っているため、 ` String +`メソッドも持つ型のみを受け入れます。

これで、 `+ Stringer `インターフェースを満たすものなら何でも ` Print +`メソッドを使用できます。 これを実証するために別のタイプを作成しましょう。

main.go

package main

import "fmt"

type Article struct {
   Title  string
   Author string
}

func (a Article) String() string {
   return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}











type Stringer interface {
   String() string
}

func main() {
   a := Article{
       Title:  "Understanding Interfaces in Go",
       Author: "Sammy Shark",
   }
   Print(a)







}

func Print(s Stringer) {
   fmt.Println(s.String())
}

次に、 `+ Book `という2番目のタイプを追加します。 また、定義された ` String `メソッドもあります。 これは、 ` Stringer `インターフェースも満たしていることを意味します。 このため、 ` Print +`関数に送信することもできます:

OutputThe "Understanding Interfaces in Go" article was written by Sammy Shark.
The "All About Go" book was written by Jenny Dolphin. It has 25 pages.

これまで、単一のインターフェイスを使用する方法を示してきました。 ただし、インターフェイスには複数の動作を定義できます。 次に、より多くのメソッドを宣言することにより、インターフェイスをより汎用的にする方法を確認します。

インターフェイス内の複数の動作

Goコードを書く中核となる原則の1つは、小さくて簡潔な型を記述し、それらをより大きく、より複雑な型に構成することです。 インターフェイスを構成する場合も同様です。 インターフェースの作成方法を確認するには、最初に1つのインターフェースのみを定義することから始めます。 「+ Circle」と「+ Square」の2つの形状を定義します。どちらも「+ Area +」というメソッドを定義します。 このメソッドは、それぞれの形状の幾何学的領域を返します。

main.go

package main

import (
   "fmt"
   "math"
)

type Circle struct {
   Radius float64
}

func (c Circle) Area() float64 {
   return math.Pi * math.Pow(c.Radius, 2)
}

type Square struct {
   Width  float64
   Height float64
}

func (s Square) Area() float64 {
   return s.Width * s.Height
}

type Sizer interface {
   Area() float64
}

func main() {
   c := Circle{Radius: 10}
   s := Square{Height: 10, Width: 5}

   l := Less(c, s)
   fmt.Printf("%+v is the smallest\n", l)
}

func Less(s1, s2 Sizer) Sizer {
   if s1.Area() < s2.Area() {
       return s1
   }
   return s2
}

各タイプは `+ Area `メソッドを宣言するため、その動作を定義するインターフェースを作成できます。 次の ` Sizer +`インターフェースを作成します。

main.go

...
type Sizer interface {
   Area() float64
}
...

次に、2つの `+ Sizer `を取り、最小のものを返す ` Less +`という関数を定義します。

main.go

...
func Less(s1, s2 Sizer) Sizer {
   if s1.Area() < s2.Area() {
       return s1
   }
   return s2
}
...

`+ Sizer `型として両方の引数を受け入れるだけでなく、結果を ` Sizer `としても返すことに注意してください。 これは、「 Circle」の「+ Square」ではなく、「+ Sizer +」のインターフェースを返すことを意味します。

最後に、面積が最小のものを印刷します。

Output{Width:5 Height:10} is the smallest

次に、各タイプに別の動作を追加しましょう。 今回は、文字列を返す `+ String()`メソッドを追加します。 これは ` fmt.Stringer +`インターフェースを満たします:

main.go

package main

import (
   "fmt"
   "math"
)

type Circle struct {
   Radius float64
}

func (c Circle) Area() float64 {
   return math.Pi * math.Pow(c.Radius, 2)
}





type Square struct {
   Width  float64
   Height float64
}

func (s Square) Area() float64 {
   return s.Width * s.Height
}





type Sizer interface {
   Area() float64
}

type Shaper interface {
   Sizer
   fmt.Stringer
}

func main() {
   c := Circle{Radius: 10}


   s := Square{Height: 10, Width: 5}


   l := Less(c, s)
   fmt.Printf("%v is the smallest\n", l)

}

func Less(s1, s2 Sizer) Sizer {
   if s1.Area() < s2.Area() {
       return s1
   }
   return s2
}

+ Circle`と + Square + の両方のタイプは + Area`と `+ String`メソッドの両方を実装しているため、別のインターフェイスを作成して、そのより広範な動作を記述することができます。 これを行うには、 `+ Shaper `というインターフェースを作成します。 これを ` Sizer `インターフェースと ` fmt.Stringer +`インターフェースで構成します。

main.go

...
type Shaper interface {
   Sizer
   fmt.Stringer
}
...

これで、 `+ Shaper `を引数として取る ` PrintArea `という関数を作成できます。 これは、 ` Area `メソッドと ` String +`メソッドの両方に渡された値で両方のメソッドを呼び出すことができることを意味します。

main.go

...
func PrintArea(s Shaper) {
   fmt.Printf("area of %s is %.2f\n", s.String(), s.Area())
}

プログラムを実行すると、次の出力が表示されます。

Outputarea of Circle {Radius: 10.00} is 314.16
area of Square {Width: 5.00, Height: 10.00} is 50.00
Square {Width: 5.00, Height: 10.00} is the smallest

これで、必要に応じて、より小さなインターフェイスを作成し、より大きなインターフェイスに構築する方法を確認しました。 大きなインターフェイスから開始してすべての関数に渡すこともできますが、必要な関数には最小のインターフェイスのみを送信することをお勧めします。 特定の小さなインターフェイスを受け入れるものはすべて、その定義された動作でのみ動作することを意図しているため、これは通常、より明確なコードになります。

たとえば、 `+ Shaper `を ` Less `関数に渡した場合、 ` Area `メソッドと ` String `メソッドの両方を呼び出すと想定できます。 ただし、 ` Area `メソッドのみを呼び出すことを意図しているため、渡された引数の ` Area `メソッドしか呼び出せないことがわかっているため、 ` Less +`関数が明確になります。

結論

小さなインターフェイスを作成し、それを大きなインターフェイスに構築することで、関数またはメソッドに必要なものだけを共有できることを確認しました。 また、パッケージだけでなく、他のパッケージで定義されたものを含む、他のインターフェースからインターフェースを構成できることも学びました。

Goプログラミング言語について詳しく知りたい場合は、https://www.digitalocean.com/community/tutorial_series/how-to-code-in-go [Goシリーズのコーディング方法]全体をご覧ください。