zoukankan      html  css  js  c++  java
  • 函数类型实现接口——把函数作为接口来调用

    Go语言函数类型实现接口——把函数作为接口来调用 http://c.biancheng.net/view/58.html

    函数和其他类型一样都属于“一等公民”,其他类型能够实现接口,函数也可以,本节将对结构体与函数实现接口的过程进行对比。

    首先给出本节完整的代码:

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. // 调用器接口
    6. type Invoker interface {
    7. // 需要实现一个Call方法
    8. Call(interface{})
    9. }
    10. // 结构体类型
    11. type Struct struct {
    12. }
    13. // 实现Invoker的Call
    14. func (s *Struct) Call(p interface{}) {
    15. fmt.Println("from struct", p)
    16. }
    17. // 函数定义为类型
    18. type FuncCaller func(interface{})
    19. // 实现Invoker的Call
    20. func (f FuncCaller) Call(p interface{}) {
    21. // 调用f函数本体
    22. f(p)
    23. }
    24. func main() {
    25. // 声明接口变量
    26. var invoker Invoker
    27. // 实例化结构体
    28. s := new(Struct)
    29. // 将实例化的结构体赋值到接口
    30. invoker = s
    31. // 使用接口调用实例化结构体的方法Struct.Call
    32. invoker.Call("hello")
    33. // 将匿名函数转为FuncCaller类型,再赋值给接口
    34. invoker = FuncCaller(func(v interface{}) {
    35. fmt.Println("from function", v)
    36. })
    37. // 使用接口调用FuncCaller.Call,内部会调用函数本体
    38. invoker.Call("hello")
    39. }

    有如下一个接口:

    1. // 调用器接口
    2. type Invoker interface {
    3. // 需要实现一个Call()方法
    4. Call(interface{})
    5. }

    这个接口需要实现 Call() 方法,调用时会传入一个 interface{} 类型的变量,这种类型的变量表示任意类型的值。

    接下来,使用结构体进行接口实现。

    结构体实现接口

    结构体实现 Invoker 接口的代码如下:

    1. // 结构体类型
    2. type Struct struct {
    3. }
    4. // 实现Invoker的Call
    5. func (s *Struct) Call(p interface{}) {
    6. fmt.Println("from struct", p)
    7. }

    代码说明如下:

    • 第 2 行,定义结构体,该例子中的结构体无须任何成员,主要展示实现 Invoker 的方法。
    • 第 6 行,Call() 为结构体的方法,该方法的功能是打印 from struct 和传入的 interface{} 类型的值。


    将定义的 Struct 类型实例化,并传入接口中进行调用,代码如下:

    1. // 声明接口变量
    2. var invoker Invoker
    3. // 实例化结构体
    4. s := new(Struct)
    5. // 将实例化的结构体赋值到接口
    6. invoker = s
    7. // 使用接口调用实例化结构体的方法Struct.Call
    8. invoker.Call("hello")

    代码说明如下:

    • 第 2 行,声明 Invoker 类型的变量。
    • 第 5 行,使用 new 将结构体实例化,此行也可以写为 s:=&Struct。
    • 第 8 行,s 类型为 *Struct,已经实现了 Invoker 接口类型,因此赋值给 invoker 时是成功的。
    • 第 11 行,通过接口的 Call() 方法,传入 hello,此时将调用 Struct 结构体的 Call() 方法。


    接下来,对比下函数实现结构体的差异。

    代码输出如下:

    from struct hello

    函数体实现接口

    函数的声明不能直接实现接口,需要将函数定义为类型后,使用类型实现结构体,当类型方法被调用时,还需要调用函数本体。

    1. // 函数定义为类型
    2. type FuncCaller func(interface{})
    3. // 实现Invoker的Call
    4. func (f FuncCaller) Call(p interface{}) {
    5. // 调用f()函数本体
    6. f(p)
    7. }

    代码说明如下:

    • 第 2 行,将 func(interface{}) 定义为 FuncCaller 类型。
    • 第 5 行,FuncCaller 的 Call() 方法将实现 Invoker 的 Call() 方法。
    • 第 8 行,FuncCaller 的 Call() 方法被调用与 func(interface{}) 无关,还需要手动调用函数本体。


    上面代码只是定义了函数类型,需要函数本身进行逻辑处理,FuncCaller 无须被实例化,只需要将函数转换为 FuncCaller 类型即可,函数来源可以是命名函数、匿名函数或闭包,参见下面代码:

    1. // 声明接口变量
    2. var invoker Invoker
    3. // 将匿名函数转为FuncCaller类型, 再赋值给接口
    4. invoker = FuncCaller(func(v interface{}) {
    5. fmt.Println("from function", v)
    6. })
    7. // 使用接口调用FuncCaller.Call, 内部会调用函数本体
    8. invoker.Call("hello")

    代码说明如下:

    • 第 2 行,声明接口变量。
    • 第 5 行,将 func(v interface{}){} 匿名函数转换为 FuncCaller 类型(函数签名才能转换),此时 FuncCaller 类型实现了 Invoker 的 Call() 方法,赋值给 invoker 接口是成功的。
    • 第 10 行,使用接口方法调用。


    代码输出如下:

    from function hello

    HTTP包中的例子

    HTTP 包中包含有 Handler 接口定义,代码如下:

    1. type Handler interface {
    2. ServeHTTP(ResponseWriter, *Request)
    3. }

    Handler 用于定义每个 HTTP 的请求和响应的处理过程。

    同时,也可以使用处理函数实现接口,定义如下:

    1. type HandlerFunc func(ResponseWriter, *Request)
    2. func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    3. f(w, r)
    4. }

    要使用闭包实现默认的 HTTP 请求处理,可以使用 http.HandleFunc() 函数,函数定义如下:

    1. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    2. DefaultServeMux.HandleFunc(pattern, handler)
    3. }

    而 DefaultServeMux 是 ServeMux 结构,拥有 HandleFunc() 方法,定义如下:

    1. func (mux *ServeMux) HandleFunc(pattern string, handler func
    2. (ResponseWriter, *Request)) {
    3. mux.Handle(pattern, HandlerFunc(handler))
    4. }

    上面代码将外部传入的函数 handler() 转为 HandlerFunc 类型,HandlerFunc 类型实现了 Handler 的 ServeHTTP 方法,底层可以同时使用各种类型来实现 Handler 接口进行处理。

    the-way-to-go_ZH_CN/11.1.md at master · unknwon/the-way-to-go_ZH_CN https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/11.1.md

    类型(比如结构体)实现接口方法集中的方法,每一个方法的实现说明了此方法是如何作用于该类型的:即实现接口,同时方法集也构成了该类型的接口。实现了 Namer 接口类型的变量可以赋值给 ai (接收者值),此时方法表中的指针会指向被实现的接口方法。当然如果另一个类型(也实现了该接口)的变量被赋值给 ai,这二者(译者注:指针和方法实现)也会随之改变。

    类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口。

    实现某个接口的类型(除了实现接口方法外)可以有其他的方法。

    一个类型可以实现多个接口。

    接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。

    即使接口在类型之后才定义,二者处于不同的包中,被单独编译:只要类型实现了接口中的方法,它就实现了此接口。

    所有这些特性使得接口具有很大的灵活性。

    第一个例子:

    示例 11.1 interfaces.go

    package main
    
    import "fmt"
    
    type Shaper interface {
    	Area() float32
    }
    
    type Square struct {
    	side float32
    }
    
    func (sq *Square) Area() float32 {
    	return sq.side * sq.side
    }
    
    func main() {
    	sq1 := new(Square)
    	sq1.side = 5
    
    	var areaIntf Shaper
    	areaIntf = sq1
    	// shorter,without separate declaration:
    	// areaIntf := Shaper(sq1)
    	// or even:
    	// areaIntf := sq1
    	fmt.Printf("The square has area: %f
    ", areaIntf.Area())
    }

    输出:

    The square has area: 25.000000

    上面的程序定义了一个结构体 Square 和一个接口 Shaper,接口有一个方法 Area()

    在 main() 方法中创建了一个 Square 的实例。在主程序外边定义了一个接收者类型是 Square 方法的 Area(),用来计算正方形的面积:结构体 Square 实现了接口 Shaper 。

    所以可以将一个 Square 类型的变量赋值给一个接口类型的变量:areaIntf = sq1 。

    现在接口变量包含一个指向 Square 变量的引用,通过它可以调用 Square 上的方法 Area()。当然也可以直接在 Square 的实例上调用此方法,但是在接口实例上调用此方法更令人兴奋,它使此方法更具有一般性。接口变量里包含了接收者实例的值和指向对应方法表的指针。

    这是 多态 的 Go 版本,多态是面向对象编程中一个广为人知的概念:根据当前的类型选择正确的方法,或者说:同一种类型在不同的实例上似乎表现出不同的行为。

    如果 Square 没有实现 Area() 方法,编译器将会给出清晰的错误信息:

    cannot use sq1 (type *Square) as type Shaper in assignment:
    *Square does not implement Shaper (missing Area method)

    如果 Shaper 有另外一个方法 Perimeter(),但是Square 没有实现它,即使没有人在 Square 实例上调用这个方法,编译器也会给出上面同样的错误。

    扩展一下上面的例子,类型 Rectangle 也实现了 Shaper 接口。接着创建一个 Shaper 类型的数组,迭代它的每一个元素并在上面调用 Area() 方法,以此来展示多态行为:

    示例 11.2 interfaces_poly.go

    package main
    
    import "fmt"
    
    type Shaper interface {
    	Area() float32
    }
    
    type Square struct {
    	side float32
    }
    
    func (sq *Square) Area() float32 {
    	return sq.side * sq.side
    }
    
    type Rectangle struct {
    	length, width float32
    }
    
    func (r Rectangle) Area() float32 {
    	return r.length * r.width
    }
    
    func main() {
    
    	r := Rectangle{5, 3} // Area() of Rectangle needs a value
    	q := &Square{5}      // Area() of Square needs a pointer
    	// shapes := []Shaper{Shaper(r), Shaper(q)}
    	// or shorter
    	shapes := []Shaper{r, q}
    	fmt.Println("Looping through shapes for area ...")
    	for n, _ := range shapes {
    		fmt.Println("Shape details: ", shapes[n])
    		fmt.Println("Area of this shape is: ", shapes[n].Area())
    	}
    }

    输出:

    Looping through shapes for area ...
    Shape details:  {5 3}
    Area of this shape is:  15
    Shape details:  &{5}
    Area of this shape is:  25

    在调用 shapes[n].Area() 这个时,只知道 shapes[n] 是一个 Shaper 对象,最后它摇身一变成为了一个 Square 或 Rectangle 对象,并且表现出了相对应的行为。

    也许从现在开始你将看到通过接口如何产生 更干净、更简单 及 更具有扩展性 的代码。在 11.12.3 中将看到在开发中为类型添加新的接口是多么的容易。

    下面是一个更具体的例子:有两个类型 stockPosition 和 car,它们都有一个 getValue() 方法,我们可以定义一个具有此方法的接口 valuable。接着定义一个使用 valuable 类型作为参数的函数 showValue(),所有实现了 valuable 接口的类型都可以用这个函数。

    示例 11.3 valuable.go

    package main
    
    import "fmt"
    
    type stockPosition struct {
    	ticker     string
    	sharePrice float32
    	count      float32
    }
    
    /* method to determine the value of a stock position */
    func (s stockPosition) getValue() float32 {
    	return s.sharePrice * s.count
    }
    
    type car struct {
    	make  string
    	model string
    	price float32
    }
    
    /* method to determine the value of a car */
    func (c car) getValue() float32 {
    	return c.price
    }
    
    /* contract that defines different things that have value */
    type valuable interface {
    	getValue() float32
    }
    
    func showValue(asset valuable) {
    	fmt.Printf("Value of the asset is %f
    ", asset.getValue())
    }
    
    func main() {
    	var o valuable = stockPosition{"GOOG", 577.20, 4}
    	showValue(o)
    	o = car{"BMW", "M3", 66500}
    	showValue(o)
    }

    输出:

    Value of the asset is 2308.800049
    Value of the asset is 66500.000000

    一个标准库的例子

    io 包里有一个接口类型 Reader:

    type Reader interface {
        Read(p []byte) (n int, err error)
    }

    定义变量 r var r io.Reader

    那么就可以写如下的代码:

    	var r io.Reader
    	r = os.Stdin    // see 12.1
    	r = bufio.NewReader(r)
    	r = new(bytes.Buffer)
    	f,_ := os.Open("test.txt")
    	r = bufio.NewReader(f)

    上面 r 右边的类型都实现了 Read() 方法,并且有相同的方法签名,r 的静态类型是 io.Reader

    备注

    有的时候,也会以一种稍微不同的方式来使用接口这个词:从某个类型的角度来看,它的接口指的是:它的所有导出方法,只不过没有显式地为这些导出方法额外定一个接口而已。

    练习 11.1 simple_interface.go:

    定义一个接口 Simpler,它有一个 Get() 方法和一个 Set()Get()返回一个整型值,Set() 有一个整型参数。创建一个结构体类型 Simple实现这个接口。

    接着定一个函数,它有一个 Simpler 类型的参数,调用参数的 Get() 和 Set() 方法。在 main 函数里调用这个函数,看看它是否可以正确运行。

    练习 11.2 interfaces_poly2.go:

    a) 扩展 interfaces_poly.go 中的例子,添加一个 Circle 类型

    b) 使用一个抽象类型 Shape(没有字段) 实现同样的功能,它实现接口 Shaper,然后在其他类型里内嵌此类型。扩展 10.6.5 中的例子来说明覆写。

    链接

  • 相关阅读:
    jquery 实现 html5 placeholder 兼容password密码框
    php返回json的结果
    使用PHP读取远程文件
    Sharepoint 自定义字段
    Sharepoint 中新增 aspx页面,并在页面中新增web part
    【转】Sharepoint 2010 配置我的站点及BLOG
    JS 实现 Div 向上浮动
    UserProfile同步配置
    【转】Import User Profile Photos from Active Directory into SharePoint 2010
    Sharepoint 2010 SP1升级后 FIMSynchronizationService 服务无法开启
  • 原文地址:https://www.cnblogs.com/rsapaper/p/9593564.html
Copyright © 2011-2022 走看看