Go 语言不是一种 “传统” 的面向对象编程语言:它里面没有类和继承的概念。
但是 Go 语言里有非常灵活的 接口 概念,通过它可以实现很多面向对象的特性。接口提供了一种方式来 说明 对象的行为:如果谁能搞定这件事,它就可以用在这儿。
interfaceinterface{}interface{}
interface{}stringintint64结构体类型interface{}interface{}
nil
var i interface{} = 99 // i可以是任何类型
i = 44.09
i = "All" // i 可接受任意类型的赋值
接口是一组抽象方法的集合,它必须由其他非接口类型实现,不能自我实现。Go 语言通过它可以实现很多面向对象的特性。
通过如下格式定义接口:
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
Namer-erReaderWriterFormatterCloseNotifiererRecoverableableI
io
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
在Go语言中,如果接口的所有方法在某个类型方法集中被实现,则认为该类型实现了这个接口。
类型不用显式声明实现了接口,只需要实现接口所有方法,这样的隐式实现解藕了实现接口的包和定义接口的包。
同一个接口可被多个类型可以实现,一个类型也可以实现多个接口。实现了某个接口的类型,还可以有其它的方法。有时我们甚至都不知道某个类型定义的方法集巧合地实现了某个接口。这种灵活性使我们不用像JAVA语言那样需要显式implement,一旦类型不需要实现某个接口,我们甚至可以不改动任何代码。
类型需要实现接口方法集中的所有方法,一定是接口方法集中所有方法。类型实现了这个接口,那么接口类型的变量也就可以存放该类型的值。
AIBf()BB
package main
import (
"fmt"
)
type A struct {
Books int
}
type B interface {
f()
}
func (a A) f() {
fmt.Println("A.f() ", a.Books)
}
type I int
func (i I) f() {
fmt.Println("I.f() ", i)
}
func main() {
var a A = A{Books: 9}
a.f()
var b B = A{Books: 99} // 接口类型可接受结构体A的值,因为结构体A实现了接口
b.f()
var i I = 199 // I是int类型引申出来的新类型
i.f()
var b2 B = I(299) // 接口类型可接受新类型I的值,因为新类型I实现了接口
// 这里I(299)表示强制类型转换
b2.f()
}
输出结果:
A.f() 9
A.f() 99
I.f() 199
I.f() 299
2. 接口的嵌入如果接口在类型之后才定义,或者二者处于不同的包中。但只要类型实现了接口中的所有方法,这个类型就实现了此接口。
注意:接口中的方法必须要全部实现,才能实现接口。
一个接口可以包含一个或多个其他的接口,但是在接口内不能嵌入结构体,也不能嵌入接口自身,否则编译会出错。
下面这两种嵌入接口自身的方式都不能编译通过:
// 编译错误:invalid recursive type Bad
type Bad interface {
Bad
}
// 编译错误:invalid recursive type Bad2
type Bad1 interface {
Bad2
}
type Bad2 interface {
Bad1
}
FileReadWriteLock Close()
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface {
ReadWrite
Lock
Close()
}
int
3. 类型断言
前面我们可以把实现了某个接口的类型值保存在接口变量中,但反过来某个接口变量属于哪个类型呢?如何检测接口变量的类型呢?这就是类型断言(Type Assertion)的作用。
IvarI
value, ok := element.(T)varIT
value, ok := varI.(T) // 类型断言,T要换成具体的类型
varIinvalid type assertion: varI.(T) (non-interface type (type of I) on left)
类型断言可能是无效的,虽然编译器会尽力检查转换是否有效,但是它不可能预见所有的可能性。如果转换在程序运行时失败会导致错误发生。更安全的方式是使用以下形式来进行类型断言:
var varI I
varI = T("Tstring")
if v, ok := varI.(T); ok { // 类型断言
fmt.Println("varI类型断言结果为:", v) // varI已经转为T类型
varI.f()
}
vvarIT oktruevTokfalse
4. 类型判断
switch
// Type-switch做类型判断
var value interface{}
switch str := value.(type) { // 这里就是type关键字
case string:
fmt.Println("value类型断言结果为string:", str)
case Stringer:
fmt.Println("value类型断言结果为Stringer:", str)
default:
fmt.Println("value类型不在上述类型之中")
}
fallthrough
5. 接口与动态类型
在经典的面向对象语言(像 C++,Java 和 C#)中,往往将数据和方法被封装为类的概念:类中包含它们两者,并且不能剥离。
Go 语言中没有类,数据(结构体或更一般的类型)和方法是一种松耦合的正交关系。Go 语言中的接口必须提供一个指定方法集的实现,但是更加灵活通用:任何提供了接口方法实现代码的类型都隐式地实现了该接口,而不用显式地声明。该特性允许我们在不改变已有的代码的情况下定义和使用新接口。
接收一个(或多个)接口类型作为参数的函数,其实参可以是任何实现了该接口的类型。 实现了某个接口的类型可以被传给任何以此接口为参数的函数 。
Go 语言动态类型的实现通常需要编译器静态检查的支持:当变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有方法。我们也可以通过类型断言来检查接口变量是否实现了相应类型。
因此 Go 语言提供了动态语言的优点,却没有其他动态语言在运行时可能发生错误的缺点。Go 语言的接口提高了代码的分离度,改善了代码的复用性,使得代码开发过程中的设计模式更容易实现。
6. 接口的提取接口的提取,是非常有用的设计模式,良好的提取可以减少需要的类型和方法数量。而且在Go语言中不需要像传统的基于类的面向对象语言那样维护整个的类层次结构。
假设有一些拥有共同行为的对象,并且开发者想要抽象出这些行为,这时就可以创建一个接口来使用。在Go语言中这样操作甚至不会影响到前面开发的代码,所以我们不用提前设计出所有的接口,接口的设计可以不断演进,并且不用废弃之前的决定。而且类型要实现某个接口,类型本身不用改变,只需要在这个类型上实现新的接口方法集。
7. 接口的继承当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)时,这个类型就可以使用(另一个类型)所有的接口方法。
类型可以通过继承多个接口来提供像多重继承一样的特性:
type ReaderWriter struct {
io.Reader
io.Writer
}
8. 接口的值方法集与指针方法集的使用
结构体中,作用于变量上的方法实际上是不区分变量到底是指针还是值的。当碰到接口类型值时,这会变得有点复杂,原因是接口变量中存储的具体值是不可寻址的,幸运的是,如果使用不当编译器会给出错误。考虑下面的程序:
package main
import (
"fmt"
)
type List []int
func (l List) Len() int {
return len(l)
}
func (l *List) Append(val int) {
*l = append(*l, val)
}
type Appender interface {
Append(int)
}
func CountInto(a Appender, start, end int) {
for i := start; i <= end; i++ {
a.Append(i)
}
}
type Lener interface {
Len() int
}
func LongEnough(l Lener) bool {
return l.Len()*10 > 42
}
func main() {
// A bare value
var lst List
// compiler error:
// cannot use lst (type List) as type Appender in argument to CountInto:
// List does not implement Appender (Append method has pointer receiver)
// CountInto(lst, 1, 10)
if LongEnough(lst) { // VALID:Identical receiver type
fmt.Printf("- lst is long enough\n")
}
// A pointer value
plst := new(List)
CountInto(plst, 1, 10) //VALID:Identical receiver type
if LongEnough(plst) {
// VALID: a *List can be dereferenced for the receiver
fmt.Printf("- plst is long enough\n")
}
}
lstCountIntoCountIntoAppenderAppendlstLongEnoughLenplstCountIntoCountIntoAppenderAppendplstLongEnough
P
x.f()(&x).f()
9. 空接口
空接口或者最小接口 不包含任何方法,它对实现不做任何要求:
type Any interface {}
Java/C#ObjectanyAny
Java/C#Object
var val interface {}
type-switchlambda
package main
import "fmt"
type specialString string
var whatIsThis specialString = "hello"
func TypeSwitch() {
testFunc := func(any interface{}) {
switch v := any.(type) {
case bool:
fmt.Printf("any %v is a bool type", v)
case int:
fmt.Printf("any %v is an int type", v)
case float32:
fmt.Printf("any %v is a float32 type", v)
case string:
fmt.Printf("any %v is a string type", v)
case specialString:
fmt.Printf("any %v is a special String!", v)
default:
fmt.Println("unknown type!")
}
}
testFunc(whatIsThis)
}
func main() {
TypeSwitch()
}
输出结果:
any hello is a special String!
9.1 构建通用类型或包含不同类型变量的数组
Elementtype Element interface{}
VectorElement
type Vector struct {
a []Element
}
VectorVectorGet()i
func (p *Vector) Get(i int) Element {
return p.a[i]
}
Set()i
func (p *Vector) Set(i int, e Element) {
p.a[i] = e
}
VectorElement
9.2 复制数据切片至空接口切片
myType
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = dataSlice
cannot use dataSlice (type []myType) as type []interface { } in assignment
原因是它们俩在内存中的布局是不一样的
for-range
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
interfaceSlice[i] = d
}
9.3 通用类型的节点数据结构
NewNodeSetData
package main
import "fmt"
type Node struct {
le *Node
data interface{}
ri *Node
}
func NewNode(left, right *Node) *Node {
return &Node{left, nil, right}
}
func (n *Node) SetData(data interface{}) {
n.data = data
}
func main() {
root := NewNode(nil, nil)
root.SetData("root node")
// make child (leaf) nodes:
a := NewNode(nil, nil)
a.SetData("left node")
b := NewNode(nil, nil)
b.SetData("right node")
root.le = a
root.ri = b
fmt.Printf("%v\n", root) // Output: &{0x125275f0 root node 0x125275e0}
}
9.4 接口到接口
GoRubyPython
假定:
var ai AbsInterface // declares method Abs()
type SqrInterface interface {
Sqr() float
}
var si SqrInterface
pp := new(Point) // say *Point implements Abs, Sqr
var empty interface{}
那么下面的语句和类型断言是合法的:
empty = pp // everything satisfies empty
ai = empty.(AbsInterface) // underlying value pp implements Abs()
// (runtime failure otherwise)
si = ai.(SqrInterface) // *Point has Sqr() even though AbsInterface doesn’t
empty = si // *Point implements empty set
// Note: statically checkable so type assertion not necessary.
下面是函数调用的一个例子:
type myPrintInterface interface {
print()
}
func f3(x myInterface) {
x.(myPrintInterface).print() // type assertion to myPrintInterface
}
xmyPrintInterfacexprint
xprintpanicif mpi, ok := x.(myPrintInterface); ok { mpi.print() }
该文章版权属于ツぃ☆ve芜情
摘录自:https://blog.csdn.net/dreaming_coder/article/details/106833700