Skip to content

インターフェース

Go 言語において、インターフェースは抽象型であり、メソッドの実装を提供せずに一連のメソッドシグネチャを定義するために使用されます。インターフェースの核となる理念は動作を記述することで、具体的な動作の実装はインターフェースを実装する型によって提供されます。インターフェースは Go 言語において多態性、疎結合、コードの再利用を実現するために広く使用されています。

概念

Go のインターフェースに関する発展の歴史には分水嶺があります。Go1.17 およびそれ以前では、公式のリファレンスマニュアルにおけるインターフェースの定義は:一連のメソッドの集合でした。

An interface type specifies a method set called its interface.

インターフェース実装の定義は以下の通りです。

A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface

翻訳すると、ある型のメソッドセットがインターフェースのメソッドセットのスーパーセットである場合、その型の値をそのインターフェース型の変数に格納できる場合、その型がそのインターフェースを実装していると言います。

しかし Go1.18 において、インターフェースの定義が変更され、インターフェースは一連の型の集合として定義されるようになりました。

An interface type defines a type set.

インターフェース実装の定義は以下の通りです。

A variable of interface type can store a value of any type that is in the type set of the interface. Such a type is said to implement the interface

翻訳すると、ある型がインターフェースの型セット内にあり、その型の値をそのインターフェース型の変数に格納できる場合、その型がそのインターフェースを実装していると言います。また、以下の追加定義も提供されています。

以下の状況において、型 T がインターフェース I を実装していると言えます

  • T はインターフェースではなく、インターフェース I の型セットの要素である
  • T はインターフェースであり、T の型セットはインターフェース I の型セットのサブセットである

T がインターフェースを実装している場合、T の値もそのインターフェースを実装しています。

Go 1.18 における最大の変化はジェネリックの追加でした。新しいインターフェースの定義はジェネリックのために提供されたものですが、以前のインターフェースの使用には全く影響を与えず、同時にインターフェースは 2 つのカテゴリに分類されました。

  • 基本インターフェース(Basic Interface):メソッドセットのみを含むインターフェースは基本インターフェースです
  • 汎用インターフェース(General Interface):型セットを含むインターフェースは汎用インターフェースです

メソッドセットとは一連のメソッドの集合であり、同様に型セットとは一連の型の集合です。

TIP

あなたはこの概念が難解で理解しにくいと感じるかもしれませんが、実際には上記の長い文章を理解する必要は全くありません。

基本インターフェース

前述したように、基本インターフェースはメソッドセットであり、一連のメソッドの集合です。

宣言

まずインターフェースがどのようなものかを見てみましょう。

go
type Person interface {
  Say(string) string
  Walk(int)
}

これは Person インターフェースで、2 つの公開メソッド WalkSay があります。インターフェース内では、関数のパラメータ名はもはや重要ではなくなりますが、パラメータ名と戻り値名を追加することも許可されています。

初期化

インターフェースだけでは初期化できません。それは単なる仕様であり、具体的な実装がないためですが、宣言することはできます。

go
func main() {
   var person Person
   fmt.Println(person)
}

出力

 <nil>

実装

まず例を見てみましょう。ある建設会社が特殊な仕様のクレーンを必要としており、クレーンの特殊な仕様と設計図を提供し、クレーンには荷役と吊り上げの機能があるべきだと示しました。建設会社はクレーンを製造する責任はなく、仕様を提供しただけです。これがインターフェースです。そこで会社 A が注文を受け、自社独自の技術を使用して究極のクレーンを製造し、建設会社に納品しました。建設会社はそれがどのように実装されたかは気にせず、究極のクレーンであるかも気にしません。荷役と吊り上げができればよいだけです。単に通常のクレーンとして使用するだけです。仕様に基づいて具体的な機能を提供する、これが実装ですインターフェースの仕様に基づいて機能を使用し、その内部実装を隠蔽する、これがインターフェース指向プログラミングです。しばらくして、究極のクレーンに故障が発生し、会社 A も倒産しました。そこで会社 B が仕様に基づいてさらに強力な巨大クレーンを製造しました。同様に荷役と吊り上げの機能を持っているため、究極のクレーンとシームレスに置き換えることができ、建設の進行に影響を与えず、建設は無事に完了しました。内部実装が変更されても機能は変わらず、以前の使用に影響を与えず、自由に置き換え可能、これがインターフェース指向プログラミングの利点です

次に Go で上記の状況を記述します。

go
// クレーンインターフェース
type Crane interface {
  JackUp() string
  Hoist() string
}

// クレーン A
type CraneA struct {
  work int // 内部フィールドが異なることは内部詳細が異なることを表す
}

func (c CraneA) Work() {
  fmt.Println("技術 A を使用")
}
func (c CraneA) JackUp() string {
  c.Work()
  return "jackup"
}

func (c CraneA) Hoist() string {
  c.Work()
  return "hoist"
}

// クレーン B
type CraneB struct {
  boot string
}

func (c CraneB) Boot() {
  fmt.Println("技術 B を使用")
}

func (c CraneB) JackUp() string {
  c.Boot()
  return "jackup"
}

func (c CraneB) Hoist() string {
  c.Boot()
  return "hoist"
}

type ConstructionCompany struct {
  Crane Crane // Crane 型に基づいてクレーンを格納するのみ
}

func (c *ConstructionCompany) Build() {
  fmt.Println(c.Crane.JackUp())
  fmt.Println(c.Crane.Hoist())
  fmt.Println("建設完了")
}

func main() {
  // クレーン A を使用
  company := ConstructionCompany{CraneA{}}
  company.Build()
  fmt.Println()
  // クレーン B に交換
  company.Crane = CraneB{}
  company.Build()
}

出力

技術 A を使用
jackup
技術 A を使用
hoist
建設完了

技術 B を使用
jackup
技術 B を使用
hoist
建設完了

上記の例から、インターフェースの実装は暗黙的であることがわかります。これは基本インターフェース実装の公式定義に対応しています。メソッドセットはインターフェースのメソッドセットのスーパーセットであるため、Go ではインターフェースを実装するために implements キーワードを使用して明示的にどのインターフェースを実装するかを指定する必要はありません。インターフェースのすべてのメソッドを実装していれば、そのインターフェースを実装したことになります。実装があれば、インターフェースを初期化できます。建設会社の構造体内部は Crane 型のメンバー変数を宣言しており、Crane インターフェースを実装したすべての値を格納できます。Crane 型の変数であるため、アクセスできるメソッドは JackUpHoist のみで、内部の他のメソッド(例えば WorkBoot)にはアクセスできません。

前述したように、任意のカスタム型はメソッドを持つことができます。したがって、実装の定義によれば、任意のカスタム型はインターフェースを実装できます。以下にいくつかの特殊な例を挙げます。

go
type Person interface {
   Say(string) string
   Walk(int)
}

type Man interface {
   Exercise()
   Person
}

Man インターフェースのメソッドセットは Person のスーパーセットであるため、Man もインターフェース Person を実装しています。ただし、これはより「継承」に似ています。

go
type Number int

func (n Number) Say(s string) string {
  return "bibibibibi"
}

func (n Number) Walk(i int) {
  fmt.Println("歩けません")
}

Number の基底型は int です。これは他の言語では非常に奇妙に見えるかもしれませんが、Number のメソッドセットは確かに Person のスーパーセットであるため、実装と見なされます。

go
type Func func()

func (f Func) Say(s string) string {
  f()
  return "bibibibibi"
}

func (f Func) Walk(i int) {
  f()
  fmt.Println("歩けません")
}

func main() {
  var function Func
  function = func() {
    fmt.Println("何かをする")
  }
  function()
}

同様に、関数型もインターフェースを実装できます。

空インターフェース

go
type Any interface{

}

Any インターフェース内部にはメソッドセットがありません。実装の定義によれば、すべての型は Any インターフェースの実装です。すべての型のメソッドセットは空集合のスーパーセットであるため、Any インターフェースは任意の型の値を格納できます。

go
func main() {
  var anything Any

  anything = 1
  println(anything)
  fmt.Println(anything)

  anything = "something"
  println(anything)
  fmt.Println(anything)

  anything = complex(1, 2)
  println(anything)
  fmt.Println(anything)

  anything = 1.2
  println(anything)
  fmt.Println(anything)

  anything = []int{}
  println(anything)
  fmt.Println(anything)

  anything = map[string]int{}
  println(anything)
  fmt.Println(anything)
}

出力

(0xe63580,0xeb8b08)
1
(0xe63d80,0xeb8c48)
something
(0xe62ac0,0xeb8c58)
(1+2i)
(0xe62e00,0xeb8b00)
1.2
(0xe61a00,0xc0000080d8)
[]
(0xe69720,0xc00007a7b0)
map[]

出力を通じて、2 つの出力結果が一致しないことがわかります。実際、インターフェース内部は (val, type) からなるタプルと見なせます。type は具体的な型で、メソッドを呼び出す際に具体的な型の具体的な値を呼び出します。

go
interface{}

これも空インターフェースですが、匿名の空インターフェースです。開発では、匿名の空インターフェースを使用して任意の型の値を受け取ることを表すために一般的に使用されます。例を以下に示します。

go
func main() {
   DoSomething(map[int]string{})
}

func DoSomething(anything interface{}) interface{} {
   return anything
}

後続の更新で、公式は別の解決策を提案しました。便宜のため、any を使用して interface{} を置き換えることができます。両者は完全に等価です。前者は単なる型エイリアスに過ぎないため、以下の通りです。

go
type any = interface{}

空インターフェースを比較する際、その基底型を比較します。型が一致しない場合は false となり、その後で値の比較を行います。例えば以下の通りです。

go
func main() {
  var a interface{}
  var b interface{}
  a = 1
  b = "1"
  fmt.Println(a == b)
  a = 1
  b = 1
  fmt.Println(a == b)
}

出力は以下の通りです。

false
true

基底の型が比較できない場合、panic が発生します。Go 而言、組み込みデータ型が比較可能かどうかの状況は以下の通りです。

比較可能基準
数値型はい値が等しいかどうか
文字列型はい値が等しいかどうか
配列型はい配列の全要素が等しいかどうか
スライス型いいえ比較不可
構造体はいフィールド値がすべて等しいかどうか
map 型いいえ比較不可
チャネルはいアドレスが等しいかどうか
ポインタはいポインタが格納するアドレスが等しいかどうか
インターフェースはい格納されているデータが等しいかどうか

Go には、すべての比較可能型を表すための専用インターフェース型があります。それは comparable です。

go
type comparable interface{ comparable }

TIP

比較できない型に対して比較を試みると、panic が発生します。

汎用インターフェース

汎用インターフェースはジェネリックのために提供されたものです。ジェネリックを習得すれば、汎用インターフェースも習得できます。ジェネリック をご覧ください。

Golang学习网由www.golangdev.cn整理维护