Goでインターフェースを使用する方法
序章
柔軟で再利用可能なモジュラーコードを作成することは、用途の広いプログラムを開発するために不可欠です。 このように作業することで、複数の場所で同じ変更を加える必要がなくなるため、コードの保守が容易になります。 これを達成する方法は、言語によって異なります。 たとえば、継承は、Java、C ++、C#などの言語で使用される一般的なアプローチです。
開発者は、構成を介して同じ設計目標を達成することもできます。 コンポジションは、オブジェクトまたはデータ型をより複雑なものに結合する方法です。 これは、Goがコードの再利用、モジュール性、および柔軟性を促進するために使用するアプローチです。 Goのインターフェースは、複雑な構成を整理する方法を提供し、それらの使用方法を学ぶことで、共通の再利用可能なコードを作成できるようになります。
この記事では、コードを再利用できるようにする、一般的な動作を持つカスタムタイプを作成する方法を学習します。 また、別のパッケージから定義されたインターフェイスを満たす独自のカスタムタイプのインターフェイスを実装する方法についても学習します。
動作の定義
コンポジションのコア実装の1つは、インターフェイスの使用です。 インターフェイスは、型の動作を定義します。 Go標準ライブラリで最も一般的に使用されるインターフェイスの1つは、fmt.Stringerインターフェイスです。
type Stringer interface {
String() string
}
コードの最初の行は、 type
と呼ばれる Stringer
. それからそれはそれが interface
. 構造体を定義するのと同じように、Goは中括弧を使用します({}
)インターフェイスの定義を囲みます。 構造体の定義と比較して、インターフェイスの動作のみを定義します。 つまり、「このタイプで何ができるか」です。
の場合 Stringer
インターフェイス、唯一の動作は String()
方法。 このメソッドは引数をとらず、文字列を返します。
次に、 fmt.Stringer
行動:
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
フィールドと両方が文字列データ型です。
...
type Article struct {
Title string
Author string
}
...
次に、メソッドを定義します。 String
に Article
タイプ。 The String
メソッドは、を表す文字列を返します Article
タイプ:
...
func (a Article) String() string {
return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}
...
次に、私たちの main
関数、インスタンスを作成します Article
入力して、呼び出された変数に割り当てます a
. 私たちはの値を提供します "Understanding Interfaces in Go"
のために Title
フィールド、および "Sammy Shark"
のために Author
分野:
...
a := Article{
Title: "Understanding Interfaces in Go",
Author: "Sammy Shark",
}
...
次に、結果を印刷します String
呼び出しによるメソッド fmt.Println
との結果を渡す a.String()
メソッド呼び出し:
...
fmt.Println(a.String())
プログラムを実行すると、次の出力が表示されます。
OutputThe "Understanding Interfaces in Go" article was written by Sammy Shark.
これまでのところ、インターフェイスは使用していませんが、動作する型を作成しました。 その振る舞いは fmt.Stringer
インターフェース。 次に、その動作を使用してコードをより再利用可能にする方法を見てみましょう。
インターフェイスの定義
目的の動作で型を定義したので、その動作の使用方法を確認できます。
ただし、その前に、 String
からの方法 Article
関数を入力します:
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(a Article) {
fmt.Println(a.String())
}
このコードでは、という新しい関数を追加します Print
それはかかります Article
引数として。 唯一のものに注意してください Print
関数は呼び出します String
方法。 このため、代わりに関数に渡すインターフェイスを定義できます。
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())
}
ここでは、というインターフェイスを作成しました Stringer
:
...
type Stringer interface {
String() string
}
...
The Stringer
インターフェイスには、呼び出されるメソッドが1つだけあります String()
それは string
. method は、Goの特定のタイプにスコープされる特別な関数です。 関数とは異なり、メソッドは、それが定義されたタイプのインスタンスからのみ呼び出すことができます。
次に、署名を更新します Print
取る方法 Stringer
、具体的なタイプではありません Article
. コンパイラはそれを知っているので Stringer
インターフェイスは、 String
メソッド、それはまた持っているタイプのみを受け入れます String
方法。
これで、 Print
を満たすものを使用するメソッド Stringer
インターフェース。 これを示すために別のタイプを作成してみましょう。
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 Book struct {
Title string
Author string
Pages int
}
func (b Book) String() string {
return fmt.Sprintf("The %q book was written by %s.", b.Title, b.Author)
}
type Stringer interface {
String() string
}
func main() {
a := Article{
Title: "Understanding Interfaces in Go",
Author: "Sammy Shark",
}
Print(a)
b := Book{
Title: "All About Go",
Author: "Jenny Dolphin",
Pages: 25,
}
Print(b)
}
func Print(s Stringer) {
fmt.Println(s.String())
}
ここで、という2番目のタイプを追加します Book
. それはまた持っています 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つのインターフェイスのみを定義することから始めます。 2つの形状を定義します。 Circle
と Square
、そしてそれらは両方とも呼ばれるメソッドを定義します Area
. このメソッドは、それぞれの形状の幾何学的領域を返します。
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
インターフェース:
...
type Sizer interface {
Area() float64
}
...
次に、という関数を定義します Less
それは2つかかります Sizer
最小のものを返します:
...
func Less(s1, s2 Sizer) Sizer {
if s1.Area() < s2.Area() {
return s1
}
return s2
}
...
タイプとして両方の引数を受け入れるだけではないことに注意してください Sizer
、ただし、結果を次のように返します。 Sizer
同じように。 これは、私たちがもはや Square
または Circle
、しかしのインターフェース Sizer
.
最後に、最小の面積を印刷します。
Output{Width:5 Height:10} is the smallest
次に、各タイプに別の動作を追加しましょう。 今回は追加します String()
文字列を返すメソッド。 これは満足します fmt.Stringer
インターフェース:
package main
import (
"fmt"
"math"
)
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * math.Pow(c.Radius, 2)
}
func (c Circle) String() string {
return fmt.Sprintf("Circle {Radius: %.2f}", c.Radius)
}
type Square struct {
Width float64
Height float64
}
func (s Square) Area() float64 {
return s.Width * s.Height
}
func (s Square) String() string {
return fmt.Sprintf("Square {Width: %.2f, Height: %.2f}", s.Width, s.Height)
}
type Sizer interface {
Area() float64
}
type Shaper interface {
Sizer
fmt.Stringer
}
func main() {
c := Circle{Radius: 10}
PrintArea(c)
s := Square{Height: 10, Width: 5}
PrintArea(s)
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
}
func PrintArea(s Shaper) {
fmt.Printf("area of %s is %.2f\n", s.String(), s.Area())
}
両方とも Circle
そしてその Square
タイプは両方を実装します Area
と String
メソッドでは、その幅広い動作のセットを記述するための別のインターフェースを作成できるようになりました。 これを行うには、というインターフェイスを作成します Shaper
. これを構成します Sizer
インターフェイスと fmt.Stringer
インターフェース:
...
type Shaper interface {
Sizer
fmt.Stringer
}
...
注:で終わることでインターフェースに名前を付けようとするのは慣用的と見なされます er
、 そのような fmt.Stringer
, io.Writer
、など。 これが、インターフェイスに名前を付けた理由です Shaper
、ではなく Shape
.
これで、という関数を作成できます PrintArea
それはかかります Shaper
引数として。 これは、渡された値に対して両方のメソッドを呼び出すことができることを意味します。 Area
と String
方法:
...
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
メソッド、それは Less
呼び出すことしかできないことがわかっているので、関数は明確です Area
渡された引数のメソッド。
結論
小さなインターフェースを作成し、それらを大きなインターフェースに構築することで、関数またはメソッドに必要なものだけを共有できるようになることを確認しました。 また、パッケージだけでなく、他のパッケージから定義されたものも含め、他のインターフェイスからインターフェイスを構成できることも学びました。
Goプログラミング言語について詳しく知りたい場合は、Goシリーズのコーディング方法全体を確認してください。