学習Go 12日目-インタフェース2、タイプ断言、内蔵インタフェース(error、Stringer、空インタフェース)

33328 ワード

インターフェース.
1.タイプブレークスルー
以前interface 1編では,interface変数は指定されたタイプの固有メソッドを使用できなかった.すなわち,Javaのようにインタフェースも実装体も存在する方法ではなく,実装体内部にのみ存在する方法がインタフェースでは利用できないようにする.
ではジャワではどうやってやったのでしょうか?タイプを変換すればいいだけです.
goのインタフェースでもタイプを変換できます.
ちなみにgoのタイプ変換は以下の通りです.
value_float := 1.2
value_int := int(value_float)
以前に使用した例を取得
package main

import "fmt"

type TapeInterface interface {
	Play(string)
	Stop()
}

type TapePlayer struct {
	Batteries string
}

func (t TapePlayer) Play(song string) {
	fmt.Println("playing", song)
}

func (t TapePlayer) Stop() {
	fmt.Println("stopped")
}

type TapeRecorder struct {
	Microphones int
}

func (t TapeRecorder) Play(song string) {
	fmt.Println("Recording", song)
}

func (t TapeRecorder) Record() {
	fmt.Println("Recording")
}

func (t TapeRecorder) Stop() {
	fmt.Println("Stopped!!")
}

func playList(device TapeInterface, songs []string) {
	for _, song := range songs {
		device.Play(song)
	}
	recorder := TapeRecorder(device)
	recorder.Record()
	device.Stop()
}

func main() {
	player := TapePlayer{}
	record := TapeRecorder{}
	mixtape := []string{"first", "second", "third"}
	playList(player, mixtape)
	playList(record, mixtape)
}
TapeRecorderインタフェースとして宣言されたデバイスをTapeRecorderタイプのレコーダに変換し、TapeRecorderにのみ存在する一意のメソッドレコードを呼び出す.
しかし、上記のコードを振り返ると、エラーが見つかります.
recorder := TapeRecorder(device)
これは、タイプ変換がインタフェースタイプに使用できないため、問題です.
では、どうやって解決するのでしょうか.
この場合はタイプ断言を使用します.
インタフェースタイプの変数に特定のタイプの値が割り当てられている場合、タイプブレークスルーを使用して特定のタイプの値を取得できます.
  • 型断言方法
  • var noiseMaker NoiseMaker = Robot("")
    var robot Robot = noiseMaker.(Robot)
    下図のようにnoiseMaker.(Robot)を使用すればよい.인터페이스.(구현체타입)です.
    インタフェースで特定の方法を使用するように.これらの問題を解決するために
    package main
    
    import "fmt"
    
    type TapeInterface interface {
    	Play(string)
    	Stop()
    }
    
    type TapePlayer struct {
    	Batteries string
    }
    
    func (t TapePlayer) Play(song string) {
    	fmt.Println("playing", song)
    }
    
    func (t TapePlayer) Stop() {
    	fmt.Println("stopped")
    }
    
    type TapeRecorder struct {
    	Microphones int
    }
    
    func (t TapeRecorder) Play(song string) {
    	fmt.Println("Recording", song)
    }
    
    func (t TapeRecorder) Record() {
    	fmt.Println("Recording")
    }
    
    func (t TapeRecorder) Stop() {
    	fmt.Println("Stopped!!")
    }
    
    func playList(device TapeInterface, songs []string) {
    	for _, song := range songs {
    		device.Play(song)
    	}
    	recorder := device.(TapeRecorder)
    	recorder.Record()
    	device.Stop()
    }
    
    func main() {
    	player := TapePlayer{}
    	record := TapeRecorder{}
    	mixtape := []string{"first", "second", "third"}
    	playList(player, mixtape)
    	playList(record, mixtape)
    }
    上記のコードは、タイプ断言recorder := device.(TapeRecorder)を使用してインタフェースのタイプをインプリメンテーションに変換し、特定のインプリメンテーションのメソッドを呼び出すことができる.ただし、コンパイルエラーは確実に解決されましたが、次のエラーメッセージが表示される可能性があります.
    panic: interface conversion: main.TapeInterface is main.TapePlayer, not main.TapeRecorder
    このような間違いが発生した原因は何ですか.
    TapeInterfaceにはTapeRecorderだけでなくTapePlayerも含まれているからです.TapePlayerインプリメンテーションが組み込まれていますが、TapeRecorderに変換できないため問題が発生します.そのためpanicが発生します.では、タイプ断言の失敗と成功をどのように区別し、コードを柔軟にするのでしょうか.
    2.タイプ断言失敗時のパニック防止
    panicはコンパイル期間ではなく実行時に発生します.
    このタイプの断言に失敗したことを特定するには、2番目の戻り値が成功したかどうかを決定します.boolタイプで戻り、失敗時はfalse、成功時はtrueです.
    var player Player = TapePlayer{}
    recorder , ok := player.(TapeRecorder)
    if ok {
        recorder.Record()
    }else{
        fmt.Println("Player was not a TapeRecorder")
    }
    これにより、実行時にクラッシュを防止できます.
    上記のpanic失敗の例を修復してみましょう.
    func playList(device TapeInterface, songs []string) {
    	for _, song := range songs {
    		device.Play(song)
    	}
    	recorder, ok := device.(TapeRecorder)
    	if ok {
    		recorder.Record()
    	}
    	device.Stop()
    }
    次の関数に置き換えると、問題なく返されます.
    3.インタフェース内蔵
    3.1エラーインタフェース
    以前Errorf()関数により過errを生成した.ここでerrはどんなタイプですか?
    err := fmt.Errorf("error hello world")
    ここでは、Errorf宣言の部分に移動してみましょう.
    func Errorf(format string, a ...interface{}) error {}
    宣言は次のように確認できます.戻りタイプはerrorです
    type error interface {
     Error() string
    }
    以下に示す.すなわち,errorはインタフェースであり,方法はerror()のみである.これにより、customerrorタイプを作成できます.
    実際,errorインタフェースをfmtに入れると,error()メソッドが呼び出されて動作する.他の言語では、例外処理は非常に複雑であり、逆にgoはエラー処理タイプを非常に簡単に作成することができる.
    package main
    
    import (
        "fmt"
    )
    
    type overHeatError float64
    
    func (o overHeatError) Error() string {
        return fmt.Sprintf("over heat is : %0.2f", float64(o))
    }
    
    func checkTemperature(actual float64, criteria float64) error {
        excess := actual - criteria
        if excess > 0 {
            return overHeatError(excess)
        }
        return nil 
    }
    
    func main() {
        err := checkTemperature(38.5, 37.5)
        if err != nil {
            fmt.Println(err)
        }
    }
    errorインタフェースを実装するoverHeapErrorを作成し、errorインタフェースを戻り値として使用できます.このようにエラーインタフェースを直接実現する実装体は,エラーがどこで発生したかをより容易に追跡できる.
    3.2 Stringerインタフェース
    Javaでは、toString()メソッドは継承されているので、印刷対象でも正常に印刷できます.
    ただし、GoangはすべてのカスタムタイプがtoString()を継承するわけではありません.したがって,これらの機能を実現するインタフェースは正常に動作する.Stringer 인터페이스です.
    type Stringer interface {
        String() string
    }
    Stringerインタフェースの定義は次のとおりです.すなわち、String() stringの方法を実施すれば、Stringerインターフェースを実現することができる.
    package main
    
    import (
        "fmt"
    )
    
    type CoffeePot string
    
    func (c CoffeePot) String() string {
        return string(c) + "coffee pot"
    }
    
    func main() {
        coffeePot := CoffeePot("cold brew")
        fmt.Println(coffeePot) // cold brewcoffee po
    }
    StringerインタフェースのString()メソッドを実装すれば、Stringerインタフェースの実装体になります.したがって、fmtメソッドにカスタムタイプが追加されても、ログは自分で実装したString()メソッドに従って出力されます.
    3.3空のインタフェース
    面白いのはfmtです.Println()にどんなカスタムタイプが入っていても、確認できます.Stringerインタフェースを実装する必要はなく、基本タイプもパラメータに入ることができます.
    どうしてそんなことができるの?
    それは、すべてのタイプのインタフェースが作成されているからです.それが空のインタフェースです.
    func Println(a ...interface{}) (n int, err error) {
        return Fprintln(os.Stdout, a...)
    }
    実際のfmt.Println()メソッドの内部宣言の様子.明確に宣言されたインタフェースではなく、可変パラメータを受け入れるinterface{}のインタフェースがあり、簡潔なインタフェース宣言が表示されます.
    このように空境界面で宣言すれば、どんなタイプのものでも受け取ることができます.
    JavaのObjectのようです.任意のタイプのインタフェースを受け入れるanyというインタフェースを作成できます
    type Any interface{}
    このようにAnyを空のインタフェースに変えて、すべてのタイプを受け入れさせます.次に、メソッドとタイプを定義します.
    package main
    
    import (
        "fmt"
    )
    
    type Any interface{}
    
    type CoffeePot struct {
        name  string
        price int
    }
    
    func (c CoffeePot) String() string {
        return fmt.Sprintf("name : %s and price : %d", c.name, c.price)
    }
    
    type TeaPot struct {
        name  string
        price int
    }
    
    func (c TeaPot) String() string {
        return fmt.Sprintf("name : %s and price : %d", c.name, c.price)
    }
    
    func my_print(any Any) {
        fmt.Println("my ", any)
    }
    
    func main() {
        coffeePot := CoffeePot{name: "Coffee capsule", price: 10}
        teaPot := TeaPot{name: "Tea capsule", price: 12}
        any := []Any{coffeePot, teaPot}
        for _, value := range any {
            my_print(value)
        }
    }
    StringerインタフェースのインプリメンテーションであるCoffeePot、TeaPotを作成します.さらに、Anyは空のインターフェースであり、CoffeePotとTeaPotはいずれもAnyのインプリメンテーションとしてAnyタイプに入ることができる.
    この関数を使用すると、my print()関数は、どのタイプのログが受信されてもStringerのString関数を使用してログを出力します.これは不要な関数かもしれませんが、複数のサーバまたは部門間の通信またはメッセージ接続構造を作成するときに、ログが混同されると、私たちの部門か他の部門かが混同されます.このため、各部門はログを個別に出力する関数を作成します.
    coffePot、teapotインスタンスを作成し、Anyタイプのスライスに入れます.そしてfor文でmy printを入れ,Anyインタフェースでfmtを自動的に受信する.Println()に移動します.
    空のインタフェースでメソッドを呼び出す場合は、タイプブレークスルーを使用してそのタイプのメソッドを取得できます.