go语言面向对象编程(接口编程)

Posted 开源你我

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go语言面向对象编程(接口编程)相关的知识,希望对你有一定的参考价值。

一.接口简介

接口在go语言中有着很重要位置,可以这样说,goroutine和channel是go语言并发模型的基石,让go语言在集群化和多核化时代成为一道亮丽的风景;而接口是go语言整个类型系统基石,接口使得go语言编程更趋于完美。人们都说,go语言在编程哲学上是变革派,而不是改良派,为什么这样说呢?当然这不仅仅是因为go语言goroutine和channel,更是因为go语言类型系统,而接口又是类型系统中很重的一部分,故而咱们说接口使得go语言更趋于完美。


1.其他语言的接口(主要基于java来讲解)


interface IFoo {

    void Bar();

}


class Foo implements IFoo{

        //......

}


class Foo : public IFoo{

        //......

};


IFoo *foo = new Foo;


即使另外有一个接口IFoo2实现了与IFoo完全一样的接口方法甚至名字也叫IFoo只不过他们是位于不同的命名空间下,编译器会认为上面类Foo实现IFoo而没有实现IFoo2接口。


上面这中接口方式属于一个,侵入式的接口,侵入式接口主要表现类需要明确声明自己实现了某一个接口,其实这种强制性接口继承是面向对象编程发展中一个让人感觉不太爽的特性。


二.深入理解go语言接口


1.非侵入式接口


在java和C++这样的语言中,接口其实侵入式的接口,这样的接口是存在一些弊端,而咱们go语言避开这种侵入式接口一些弊端。在go语言中,一个类只需要实现了该接口所要求所有函数,我们就说这个类实现了该接口。


type File struct {

        //......

}


func (f *File ) Read(buf, []byte) (n int, err error)

func (f *File ) Write(buf, []byte) (n int, err error)

func (f *File ) Seek(off int64, whence int) (pos int64, err error)

func (f *File ) Close(buf, []byte)  err error


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(buf, []byte)  err error

}


type IReader interface {

          Read(buf, []byte) (n int, err error)

}


type IWriter interface {

           Write(buf, []byte) (n int, err error)

}


type IClose interface {

         Close(buf, []byte)  err error

}


尽管File类并没有从这些接口继承,甚至可以不知道有这些接口的存在,但是File类实现了这些接口,可以进行:


var file1 IFile = new(File)

var file2 IReader = new (File)

var file3 IWriter = new(File)

var file4 ICloser = new(File)


go语言的非侵入式接口,看似只是只做了很小文法调整,实则影响深远。


1).go语言的标准库,再也不需要绘制类库的继承树图。


2).实现类的时候,只需要关心自己应该提供哪些方法,不用再纠结接口需要拆分得多细才合理。接口有使用方按需定义,而不用事先规划。


3).不用为了实现一个接口二导入一个包,因为多引包的话,就意味着耦合性多。接口的使用方按照自身的需求来定义,使用无需关心是否有其他模块定义过类似的接口。


2.接口赋值


go语言中的接口赋值分为两种情况

1). 将对象实例赋值给接口

将对象实例赋值给接口,这要求该对象实例实现了接口要求的所有方法,例子如下:


type Integer int


func (a Integer) Less (b Integer) bool {

        return a < b

}


func (a Integer) Add (b Integer) {

        *a += b

}


type LessAddress interface {

        Less (a Integer) bool

        Add(a Integer)

}


var a interface = 1

var b LessAddress = &a   *


使用带星号的赋值语句,go语言可以根据下面函数:


func (a Integer) Less (b Integer) bool 


自动生成一个新的Less方法:


func (a *Integer) Less (b Integer) bool {

        return (*a).Less(b)

}


验证将对象实例赋值给接口的赋值var b LessAddress = &a 和var b LessAddress = a在编译器中都能编译通过


type Lesser interface {

        Less(b Integer) bool

}


定义一个Integer的对象实例,然后将其赋值给Lesser接口


var a Integer = 1

var b1 Lesser = &a

var b2 Lesser = a


以上两种形式在编译器中都能编译通过


案例完整代码:

package main
import "fmt"
type Integer int
func (a Integer) Less (b Integer) bool {
return a < b
}
/*
func (a *Integer) Less (b Integer) bool {
  return (*a).Less(b)
}
*/
func (a *Integer) Add (b Integer) {
*a += b
fmt.Println(a)
}
/*
func (a Integer) Add (b Integer) {
  (&a).Add(b)
}
*/
type LessAddress interface {
Less(b Integer) bool
Add(a Integer)
}

type Lesser interface {
Less(b Integer) bool
}
func main(){
var a Integer = 1
 
var b1 Lesser = &a
var b2 Lesser = a
}

2).将接口赋值给另一个接口


在go语言中,只要两个接口拥有相同的方法列表(次序不要紧),那么这两个接口是等同,他们可以相互赋值。

package one
type ReadWriter interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
}
package two
type IStream interface {
Write(buf []byte) (n int, err error)
Read(buf []byte) (n int, err error)
}

上面两个接口,方法列表相同,只是方法列表次序不一样。

在go语言中,这两个接口是没有区别,原因如下:
1).任何实现了one.ReadWriter接口的类,均实现了two.IStream
2).任何one.ReadWriter接口对象可以复制给two.IStream,反之成立
3).在任何地方使用one.ReadWriter接口与使用two.IStream并没有任何差异

验证:下面的代码在编译器中并没有报错:
var file1 two.IStream = new(os.File)
var file2 one.ReadWriter = file1
var file3 two.IStream = file2

接口赋值其实并不要求两个接口必须等价,如果接口A中的方法列表是接口B中的方法列表的子集,那么接口B可以复制给接口A,案例如下:

package three
type Writer interface {
Write(buf []byte) (n int, err error)
}
var file0  one.ReadWriter = new(os.File)
var file1 two.IStream = new(os.File)

var file4 three.Writer = file1
var file7 three.Writer = file0
var file6 three.Writer = new(os.File)
var file5 two.IStream = file6 //这样是编译不过的
3.接口查询
接口查询,就是在一个接口变量中,去判断,那个把值赋给接口变量的对象,
究竟有没有实现另一个接口,这个所谓的另一个接口,就是我要查询的那个
接口。在这个已知的接口变量中,查询另一个接口的过程,就是接口查询了。
通过接口查询,我们可以操作这个还原赋值给这个接口变量的对象的更加
全面的功能。如赋值给这个接口变量的对象,实现了很多个接口,当他
把值赋给这个接口后,那么这个接口变量就只能操作这个接口定义的方法了,
相当于这个对象的一些功能就丢失了。通过接口查询,我们来充分的挖掘
这个对象的更多功能。
案例代码:
package main

import (
"fmt"
)

type R interface {
Read()
}

type W interface {
Write(name string)
}

type RW interface {
R
W
}

type log struct {
name []string
r int
}

func (t *log) Read() {
if len(t.name) > t.r {
fmt.Println(t.name[t.r])
t.r++
} else {
fmt.Println("empty")
}
}
func (t *log) Write(name string) {
t.name = append(t.name, name)
fmt.Println("wirte success.", t.name)
}

func main() {
var r R = &log{}
var w W = &log{}
w.Write("write first")
w.Write("write second")
r.Read()
r.Read()
val, ok := w.(RW)
if !ok {
fmt.Println("hi")
} else {
val.Read()
val.Read()
}
}

4.类型查询
就是不知道一个变量的具体类型,是不是很奇怪,对于严格类型检查的
语言中,怎么会不知道变量类型了?golang中的interface{}就是其中
一种,一个函数,接收任何类型的变量作为参数,对于不同类型的参数,
采用不同的处理方法,这样就需要类型查询。
案列:
var whichType interface{}
//第一种方式:
obj,ok:=whichType.(bufio.Reader)
//如果whichType是一个bufio.Reader类型的变量,则ok为true,否则为false,当为true时,obj就是一个bufio.Reader类型的变量。

//第二种方式:
switch val := whichType.(type){
   case string:
   case int:
   default:
fmt.Println(val)
}
//case条件匹配成功后,val就是匹配的那个对象的一个实例。

5.接口组合
像之前介绍的类型组合一样,go语言中同样支持接口组合。

下面ReadWriter接口将基本的Read和Write方法组合起来了
type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}

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

6.Any类型

在go语言中,任何对象都满足空接口interface{},所以Interface()看起来
像是可以指向任何Any类型。

var v1 interface{} = 1
var v2 interface{} = "abc"
var v3 interface{} = &a
var v4 interface{} = struct {x int}{1}
var v5 interface{} = &struct {x int}{1}

当函数可以接受任意类型的实例时,我们可以将参数声明interface{},最典
型的例子就fmt中的PrintXXX系列的函数。

func Printf(fmt string, args ... interface{})
func Println(args...interface{})

总结,interface{}类似COM中IUnknown,我们刚开始的时候对其是一无所知,
但是可以通过累心查询和接口查询来逐步理解它。

7.完整案例


1、目录结构

$ tree
.
├── mplayer.go
└── src
    ├── mlib
    │ ├── manager.go
    │ └── manager_test.go
    └── mp
        ├── mp3.go
        └── play.go

1.2)mlib库的代码
1.2.1)manager.go代码
package library

import "errors"

type MusicEntry struct {
    Id string
    Name string
    Artist string
    Genre string
    Source string
    Type string
}

type MusicManager struct {
    musics []MusicEntry
}

func NewMusicManager() * MusicManager {
    return &MusicManager {make( []MusicEntry, 0)}
}

func (m * MusicManager) Len() int {
    return len(m.musics);
}

func (m* MusicManager ) Get(index int) (music * MusicEntry, err error) {
    if index < 0 || index >= len(m.musics) {
        return nil, errors.New("Index out of range.")
    }

    return &m.musics[index], nil
}

func (m * MusicManager) Find (name string) * MusicEntry {
    if len(m.musics) == 0 {
        return nil
    }

    for _,m := range m.musics {
        if m.Name == name {
            return &m
        }
    }
    return nil
}

func (m * MusicManager) Add (music * MusicEntry) {
    m.musics = append(m.musics, * music)
}

func (m * MusicManager) Remove (index int) * MusicEntry {
    if index < 0 || index >= len(m.musics) {
        return nil
    }
    removedMusic := &m.musics[index]

    if index < len(m.musics) - 1 {
        m.musics = append(m.musics[:index - 1], m.musics[index + 1:]...)
    }else if index == 0 {
        m.musics = make([]MusicEntry, 0)
    } else {
        m.musics = m.musics[:index - 1]
    }

    return removedMusic
}

func (m * MusicManager) RemoveByName (name string) * MusicEntry {
    var removedMusic * MusicEntry = nil
    var iPos int = -1
    for i := 0; i < m.Len(); i++ {
        if m.musics[i].Name == name {
            iPos = i
            break
        }
    }

    if iPos < 0 {
        return nil
    }

    removedMusic = m.Remove(iPos)

    return removedMusic
}
  
1.2.2)manager.go的测试代码manager_test.go

package library

import (
    "testing"
)

func TestOps(t * testing.T) {
    mm := NewMusicManager()
    if mm == nil {
        t.Error("NewMusicManager faild.");
    }
    if mm.Len() != 0 {
        t.Error("NewMusicManager faild, not empty")
    }
    m0 := &MusicEntry { "1", "My Heart Will Go On", "Celion Dion", "Pop", "http://qbox.me/24501234", "MP3" }
    mm.Add(m0)

    if mm.Len() != 1 {
        t.Error("MusicManager.Add faild.")
    }

    m := mm.Find(m0.Name)
    if m == nil {
        t.Error("MusicManager.Find faild")
    }

    if    m.Id != m0.Id ||
        m.Name != m0.Name ||
        m.Artist != m0.Artist ||
        m.Genre != m0.Genre ||
        m.Source != m0.Source ||
        m.Type != m0.Type {
            t.Error("MusicManager.Find() faild. Found item mismatch.")
    }

    m, err := mm.Get(0)
    if m == nil {
        t.Error("MusicManager.Get() faild.", err)
    }

    m = mm.Remove(0)
    if m == nil || mm.Len() != 0 {
        t.Error("MusicManager.Remove() faild.", err)
    }
}

1.3
)mp库代码

1.3.1)src/mp/mp3.go代码

package mp

import (
    "fmt"
    "time"
)

type MP3Player struct {
    stat int
    progress int
}

type WAVPlayer struct {
    stat int
    progress int
}

func (p * MP3Player) Play (source string) {
    fmt.Println("Playing MP3 music", source)

    p.progress = 0

    for p.progress < 100 {
        time.Sleep(100 * time.Millisecond)
        fmt.Print(".")
        p.progress += 10
    }
    fmt.Println("\nFinished playing", source)
}

func (p * WAVPlayer) Play (source string) {
    fmt.Println("Playing WAV music", source)

    p.progress = 0

    for p.progress < 100 {
        time.Sleep(100 * time.Millisecond)
        fmt.Print(".")
        p.progress += 10
    }
    fmt.Println("\nFinished playing", source)
}
 
1.3.2)src/mp/play.go代码

package mp

import "fmt"

type Player interface {
    Play(source string)
}

func Play(source, mtype string) {
    var p Player

    switch mtype {
    case "MP3" :
        p = &MP3Player{}
    case "WAV" :
        p = &WAVPlayer{}
        default :
        fmt.Println("Unsupported music type", mtype)
        return
    }
    p.Play(source)
}
  
1.4)main package模块代码mplayer.go

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"

    "mlib"
    "mp"
)

var lib * library.MusicManager
var id int = 1
var ctrl, signal chan int

func handleLibCommands(tokens []string) {
    switch tokens[1] {
    case "list" :
        for i := 0; i < lib.Len(); i++ {
            e, _ := lib.Get(i)
            fmt.Println(i + 1, ":", e.Name, e.Artist, e.Source, e.Type)
        }
    case "add" :
        if len(tokens) == 7 {
            id++
            lib.Add(&library.MusicEntry { strconv.Itoa(id), tokens[2], tokens[3], tokens[4], tokens[5], tokens[6] })
        } else {
            fmt.Println("USAGE : lib add <name><artist><genre><source><type> (7 argv)")
        }
    case "remove" :
        if len(tokens) == 3 {
            lib.RemoveByName(tokens[2])
        } else {
            fmt.Println("USAGE: lib remove <name>")
        }
        default :
        fmt.Println("Unrecogized lib command: ", tokens[1])
    }
}

func handlePlayCommands(tokens []string) {
    if len(tokens) != 2 {
        fmt.Println("USAGE : play <name>")
        return
    }

    e := lib.Find(tokens[1])
    if e == nil {
        fmt.Println("The music", tokens[1], "does not exist.")
        return
    }

    mp.Play(e.Source, e.Type)
}

func main() {
    fmt.Println(`
    Enter following commands to control the player:
    lib list --View the existing music lib
    lib add <name><artist><genre><source><type> -- Add a music to the music lib
    lib remove <name> --Remove the specified music from the lib
    play <name> -- Play the specified music
    `)
    lib = library.NewMusicManager()

    r := bufio.NewReader(os.Stdin)

    for i := 0; i <= 100; i++ {
        fmt.Print("Enter command-> ")
        rawLine, _, _ := r.ReadLine()

        line := string(rawLine)
        if line == "q" || line == "e" {
            break
        }
        tokens := strings.Split(line, " ")

        if tokens[0] == "lib" {
            handleLibCommands(tokens)
        } else if tokens[0] == "play" {
            handlePlayCommands(tokens)
        } else {
            fmt.Println("Unrecognized command :", tokens[0])
        }
    }
}
  


以上是关于go语言面向对象编程(接口编程)的主要内容,如果未能解决你的问题,请参考以下文章

Go Web编程实战----面向对象编程

Go Web编程实战----面向对象编程

Go Web编程实战----面向对象编程

go语言学习——面向对象编程

15. 面向对象编程:接口与多态

#私藏项目实操分享#Go 语言入门很简单:Go 如何面向对象