go 语言如何用接口实现多态?本文做了详细介绍。
1. 其他语言的接口
除Go以外的其他语言,为了实现一个接口,必须显式从该接口继承:
interface IFoo {
void Bar();
}
// java
class Foo implements IFoo {
// ...
}
// C++
class Foo : public IFoo {
// ...
}
IFoo* foo = new Foo;
2. Go中的接口
在面向对象领域中,我们一般这么介绍接口:
接口定义一个对象的行为。但是仅仅只定义了对象可以做什么,实现细节交给对象本身去确定。
method signature
接口使用方法
1. 定义接口
定义一个接口可以进行文件读写:
type IFile interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Seek (off int64, whence int) (pos int64, err error)
Close() error
}
2. 实现接口
FileIFile
type File struct {
}
func (f *File) Read(buf []byte) (n int, err error) {
// ...
return
}
func (f *File) Write(buf []byte) (n int, err error) {
// ...
return
}
func (f *File) Seek(off int64, whence int) (pos int64, err error) {
// ...
return
}
func (f *File) Close() error {
// ...
return nil
}
3. 接口赋值
接口赋值在Go中包含两种情况:
- 将对象实例赋值给接口
- 将一个接口赋值给另一个接口
3.1 将对象实例赋值给接口
我们现在构造一个特殊的例子,意在指出将对象实例赋值给接口时可能出现的错误:
/*
IBook接口
*/
type IBook interface {
// 获取书籍名称
GetBookName() (name string)
// 修改书籍名称
ChangeBookName(newName string)
}
/*
定义MyBook类型实现了IBook接口
*/
type MyBook struct {
Name string
}
// 值传递: 不修改数据成员
func (mb MyBook) GetBookName() (name string) {
return mb.Name
}
// 指针传递: 修改数据成员
func (mb *MyBook) ChangeBookName(newName string) {
mb.Name = newName
}
如果我们要将对象赋值给接口,那么下述两种写法是不同的:
// 构造MyBook类型的对象实例
var mb = MyBook{
Name: "TOMO-CAT",
}
// 将对象实例赋值给接口
var book IBook = &mb // 正确
var book IBook = mb // 错误
错误的语句编译期间报错如下:
./main.go:10:6: cannot use mb (type MyBook) as type IBook in assignment:
MyBook does not implement IBook (ChangeBookName method has pointer receiver)
Compilation finished with exit code 2
*MyBookGetBookName()ChangeBookName()IBook
// 值传递: 不修改数据成员
func (mb MyBook) GetBookName() (name string) {
return mb.Name
}
// 以下是Go编译器根据上述值传递版本的方法成员自动生成的
func (mb *MyBook) GetBookName() (name string) {
return (*mb).Name
}
MyBookChangeBookName()ChangeBookName()BookNameMyBookIBook
3.2 将一个接口赋值给另一个接口
在Go语言中只要两个接口有相同的方法集合,那么就可以相互赋值(在Go中认为这两个接口是等价的)。举个例子,下述两个接口定义在不同的包中,但是定义相同的方法集合:
package foo
type IFoo interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
}
package bar
type IBar interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
}
在Go语言中这两个接口是等价的,可以互相赋值:
func main() {
var file1 foo.IFoo
var file2 bar.IBar
file1 = file2
file2 = file1
return
}
另外接口赋值并不要求两个接口完全等价,如果接口A方法集合是接口B方法集合的子集,那么接口B可以赋值给接口A,反之不成立:
type Writer interface {
Write(buf []byte) (n int, err error)
}
func main() {
var file1 foo.IFoo
var file3 Writer
file3 = file1 // 正确
file1 = file3 // 错误
return
}
4. 类型查询
interface{}
interface{}
type Stringer interface {
String() string
}
// Println的简单实现
func Println(args ...interface{}) {
for _, arg := range args {
switch v := arg.(type) {
// 查询是否是int, string等内置类型
case int:
// ...
case string:
// ...
default:
// 查询是否实现了某接口
if v, ok := arg.(Stringer); ok {
val := v.String()
// ...
} else {
// ...
}
}
}
}
5. 接口组合
io.Readerio.Writerio.ReadWriter
type ReadWriter interface {
Reader
Writer
}
接口实现多态
/*
IBook接口
*/
type IBook interface {
// 获取书籍名称
GetBookName() (name string)
}
/*
定义MyBook类型实现了IBook接口
*/
type MyBook struct {
Name string
}
func (mb MyBook) GetBookName() (name string) {
return mb.Name
}
type TestBook struct {
Name string
}
func (tb TestBook) GetBookName() (name string) {
return "TestBook"
}
func main() {
book1 := MyBook{
Name: "TOMOCAT",
}
book2 := TestBook{}
bookList := []IBook{book1, book2}
for _, book := range bookList {
fmt.Println(book.GetBookName())
}
return
}