zoukankan      html  css  js  c++  java
  • 接口——定义,实现接口的条件,类型与接口的关系,类型断言

    1、定义

    Go 语言的接口设计是非侵入式的,接口编写者无须知道接口被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口,但无须指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。

    每个接口类型由数个方法组成。接口的形式代码如下:

    type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2

    }

    说明:

    • 接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
    • 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
    • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略

    2、实现接口的条件

    如果一个任意类型 T 的方法集为一个接口类型的方法集的超集,则我们说类型 T 实现了此接口类型。T 可以是一个非接口类型,也可以是一个接口类型。
    实现关系在 Go语言中是隐式的。两个类型之间的实现关系不需要在代码中显式地表示出来。Go语言中没有类似于 implements 的关键字。 Go编译器将自动在需要的时候检查两个类型之间的实现关系。
    接口定义后,需要实现接口,调用方才能正确编译通过并使用接口。接口的实现需要遵循两条规则才能让接口可用。

    1)接口的方法与实现接口的类型方法格式一致

    在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称、参数列表、返回参数列表。也就是说,只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现。

    2)接口中所有方法均被实现

    当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用。

    示例:

    package main
    import (
    	"fmt"
    )
    // 定义一个数据写入器
    type DataWriter interface {
    	WriteData(data interface{}) error
    }
    // 定义文件结构,用于实现DataWriter
    type file struct {
    }
    // 实现DataWriter接口的WriteData方法
    func (d *file) WriteData(data interface{}) error {
    	fmt.Println("WriteData:", data)
    	return nil
    }
    func main() {
    	// 实例化file
    	f := new(file)
    	// 声明一个DataWriter的接口
    	var writer DataWriter
    	// 将接口赋值f,也就是*file类型
    	writer = f
    	// 使用DataWriter接口进行数据写入
    	writer.WriteData("data")
    }
    

      

    3、类型与接口的关系

    在 Go语言中类型和接口之间有一对多和多对一的关系。

    1)一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。

    package main
    
    import (
    	"fmt"
    	"io"
    )
    
    type Writer interface {
    	Write(p []byte) (n int, err error)
    }
    type Closer interface {
    	Close() error
    }
    type Socket struct {
    }
    func (s *Socket) Write(p []byte) (n int, err error) {
    	fmt.Println("Socket write")
    	return 0, nil
    }
    func (s *Socket) Close() error {
    	fmt.Println("Socket close")
    	return nil
    }
    // 使用io.Writer的代码, 并不知道Socket和io.Closer的存在
    func usingWriter( writer io.Writer){
    	writer.Write( nil )
    }
    // 使用io.Closer, 并不知道Socket和io.Writer的存在
    func usingCloser( closer io.Closer) {
    	closer.Close()
    }
    func main() {
    	// 实例化Socket
    	s := new(Socket)
    	usingWriter(s)
    	usingCloser(s)
    }
    

      

    2)多个类型可以实现相同的接口

    一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。也就是说,使用者并不关心某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的。

    示例:

    package main
    
    import "fmt"
    
    // 一个服务需要满足能够开启和写日志的功能
    type Service interface {
    	Start()  // 开启服务
    	Log(string)  // 日志输出
    }
    // 日志器
    type Logger struct {
    }
    // 实现Service的Log()方法
    func (g *Logger) Log(l string) {
    	fmt.Println(l)
    }
    // 游戏服务
    type GameService struct {
    	Logger  // 嵌入日志器
    }
    // 实现Service的Start()方法
    func (g *GameService) Start() {
    	fmt.Println("游戏服启动")
    }
    func main() {
    	var s Service = new(GameService)
    	s.Start()
    	s.Log("hello")
    }
    

      

     4、类型断言

    Go语言中有四种接口相关的类型转换情形:

    • 将一个非接口值转换为一个接口类型。在这样的转换中,此非接口值的类型必须实现了此接口类型。
    • 将一个接口值转换为另一个接口类型(前者接口值的类型实现了后者目标接口类型)。
    • 将一个接口值转换为一个非接口类型(此非接口类型必须实现了此接口值的接口类型)。
    • 将一个接口值转换为另一个接口类型(前者接口值的类型可以实现了也可以未实现后者目标接口类型)。

    一个类型断言表达式的语法为 i.(T),其中 i 为一个接口值, T 为一个类型名或者类型字面表示。 类型 T 可以为任意一个非接口类型,或者一个任意接口类型。
    在一个类型断言表达式 i.(T) 中, i 称为断言值, T 称为断言类型。 一个断言可能成功或者失败。


    对于 T 是一个非接口类型的情况,如果断言值 i 的动态类型存在并且此动态类型和 T 为同一类型,则此断言将成功;否则,此断言失败。 当此断言成功时,此类型断言表达式的估值结果为断言值 i 的动态值的一个复制。可以把此种情况看作是一次拆封动态值的尝试。


    对于 T 是一个接口类型的情况,当断言值 i 的动态类型存在并且此动态类型实现了接口类型 T,则此断言将成功;否则,此断言失败。 当此断言成功时,此类型断言表达式的估值结果为一个包裹了断言值i的动态值的一个复制的 T 值。


    一个失败的类型断言的估值结果为断言类型的零值。

    按照上述规则,如果一个类型断言中的断言值是一个零值 nil 接口值,则此断言必定失败。

    对于大多数场合,一个类型断言被用做一个单值表达式。 但是,当一个类型断言被用做一个赋值语句中的唯一源值时,此断言可以返回一个可选的第二个结果并被视作为一个多值表达式。此可选的第二个结果为一个类型不确定的布尔值,用来表示此断言是否成功了。

    注意:如果一个断言失败并且它的可选的第二个结果未呈现,则此断言将造成一个恐慌。

    示例1:断言类型为非接口类型

    package main
    import "fmt"
    func main() {
    	// 编译器将把123的类型推断为内置类型int。
    	var x interface{} = 123
    	// 情形一:
    	n, ok := x.(int)
    	fmt.Println(n, ok) // 123 true
    	n = x.(int)
    	fmt.Println(n) // 123
    	// 情形二:
    	a, ok := x.(float64)
    	fmt.Println(a, ok) // 0 false
    	// 情形三:
    	a = x.(float64) // 将产生一个恐慌,抛出异常
    }
    

      

    示例2:

    package main
    import "fmt"
    type Writer interface {
    	Write(buf []byte) (int, error)
    }
    type DummyWriter struct{}
    func (DummyWriter) Write(buf []byte) (int, error) {
    	return len(buf), nil
    }
    func main() {
    	var x interface{} = DummyWriter{}
    	// y的动态类型为内置类型string。
    	var y interface{} = "abc"
    	var w Writer
    	var ok bool
    	// DummyWriter既实现了Writer,也实现了interface{}。
    	w, ok = x.(Writer)
    	fmt.Println(w, ok) // {} true
    	x, ok = w.(interface{})
    	fmt.Println(x, ok) // {} true
    	// y的动态类型为string。string类型并没有实现Writer。
    	w, ok = y.(Writer)
    	fmt.Println(w, ok) // <nil> false
    	w = y.(Writer) // 将产生一个恐慌
    }
    

      

  • 相关阅读:
    第一次结对作业
    第二次编程作业
    第一次编程作业
    第一次博客作业*
    个人总结
    第三次个人作业
    第二次结对作业
    第一次结对作业
    第二次个人编程作业
    第一次个人编程作业
  • 原文地址:https://www.cnblogs.com/ACGame/p/11923505.html
Copyright © 2011-2022 走看看