golang学习笔记(6)--面向接口编程

一、    duck typing

duck typing意思是鸭子类型,我们把具备鸭子的行为等部分特征的一个东西叫做鸭子,这是鸭子类型的解释。其实,在go语言中是采用鸭子类型这种思想来实现接口这种编程方式的,我们把一个类只要实现了某接口的方法,我们就说他是这个接口的实现类。如下:

我们定义了一个接口:

type duck interface {
   Get(s string) string //不用func修饰这个方法
}

下面写一个类:
type TheClass struct {
   S string
}
这个类实现了上面接口中的方法,如下,并不需要声明我们要实现某个接口。
func (tc TheClass) Get(s string) string  {
   return tc.S;
}

实现类前面的类型是属于值类型传递,因为我们的接口里面实际是包了一个类型信息与及指针,所以实现一般采用直接的值传递而不是指针传递,因为一样可以对内容修改。

如上通过go语言的方式来说明了什么是鸭子类型。

二、    go语言中的接口。

上面讲过了接口的实现方式。

type duck interface {
   Get(s string) string
}

之前我们有讲过两种go语言的两种“继承“方式,一种是类似于装饰的叫做组合,在类里面放”父类“,另一种是取别名,当然取别名算不上,他不可以增加成员变量。那么接口可以”继承“吗?嗯,当然可以,如下

type littleDuck interface {
   duck
   A() string
}
这样应该叫接口的“继承”
刚刚在golang社区看到一篇被喷的文章,下面有个人这样说:“golang的接口是非入侵式的,楼主让非得把(**)接口写入到(**)结构中,真是人才啊“
不是很理解什么意思,难道不能够用接口继承吗?问题先留在这里,以后再解决。
(n分钟过后)
查了下什么叫入侵式与非入侵式,附上自己的理解:入侵式是指要申明接口实现了某一个接口什么的,但是非入侵式就不用,一般的oo语言例如java就是入侵式接口设计,需要说明实现,有层级,但是go语言提倡是非入侵式,相对入侵式可能更灵活一点,不那么多依赖,但是这两种设计都各有优点,什么优点?看完下面的就知道了,接下来用例子来说下自己的理解吧。
à: 
Bird 接口 : fly(),run();
Chicken 接口 : run()
Wild 接口 :fly()
如上3个接口,在入侵式接口设计中,chicken接口与wild接口需要去继承bird接口,因为他们属于这个大类,那么我们创建一个chicken的实现类对象的话,只需要实现chicken,然后wild同理。这样做就是入侵式接口设计的思路。
在java中像这样:
interface Bird{
    void run();
    void fly();
}
interface Wild extends Bird{
    void run();
}
interface Chicken extends Bird{
    void fly();
}
如果我们想要创建一个Wild的类型的类那么就需要创建一个,需要给wild写个实现类,一个Chicken类型的类就需要写一个Chicken实现类,Bird就需要写一个Bird的实现类,显然接口的复用性不高。当然这样的类型是相当清晰的。
同样有另外一种写法,这样算是入侵式设计的思想,如下:
interface Run{
    void run();
}
interface Fly{
    void fly();
}

interface Bird1 extends Run,Fly{}
java的接口继承是支持多继承的
这样的写法是一种组装的思想,这也是oo语言中的入侵式接口设计思路,通过继承的方式组装好,然后去写Bird1的实现类,没什么问题。
再看看非入侵式:go语言推荐使用非入侵式接口设计,在写实现类的时候不需要说明实现哪个接口,也不需要去思考这是哪个接口的实现类,只要实现相应的方法就行,一般推荐这样写:
type chicken interface {
   run()
}
type wild interface {
   fly()
}
type BirdImpl struct {
}
func (b BirdImpl) run()  {
}
func (b BirdImpl) fly(){
}
如上的方式很灵活,我们直接可以birdImpl创建一个类,然后实现一个方法他就属于某一个类型,不用去组装接口。当然,我们也可以通过实现接口的方式来实现接口,如:
type Bird interface {
   run()
   fly()
}
type chicken interface {
   run()
}
type wild interface {
   fly()
}
这样的方式相当于bird实现了两个接口的接口,我要创建实体对象的时候需要再创建一个类:
type BirdImpl struct {}
func (b BirdImpl) run(){}
func (b BirdImpl) fly(){}
这个类从这里看必定就实现了上述的3个接口,这样的写法其实是可以的,但是看起来冗余了许多,我完全可以不需要Bird 或者另外两个接口。Go语言这样的接口设计方式相对更简单、灵活了。当然为了更具解释性,我们可以把wild名改成Fly,chicken改成Run。当然这样的组合方式在go语言使用还是不少,因为通过组合的方式也很方便灵活
那么我们可以这样来设计go语言的接口:
type Bird interface {
   run()
   fly()
}
type BirdImpl struct {}
func (b BirdImpl) run()  {}
func (b BirdImpl) fly(){}
或者这样:
type Run interface {
   run()
}
type Fly interface {
   fly()
}
type BirdImpl struct {}
func (b BirdImpl) run()  {}
func (b BirdImpl) fly(){}
这样就是非入侵式接口设计,不在接口使用继承。好像最后总结下爱就这么一句话,不在接口关系中使用继承与及接口实现。
 刚刚留了一个问题,入侵式与非入侵式的比较,从上面也可以看到入侵式接口设计类别层级什么的十分清晰,没有那么多依赖,解释性也比较好。非入侵式呢?相对比较灵活,简单,接口复用率高。(这都是我的理解哈,不代表官方说法)
网上对入侵式与非入侵式的观点很多,以上只是我的理解,而且go语言的源码里面也有很多组合接口的地方,我也很难说,这两个概念就先放一放。
三、               接口的使用。
首先说下接口变量的结构,其内部有两个东西,一个是接口实现者的类型,一个是接口实现者的指针,我们来看一段段代码:
var o A
 o = class.B{}
 fmt.Printf("%T,%p",o,o)
这里的B实现了接口A,T是类型,v是值,可以知道o里面实际含有两个人东西。同时要提下,如果在不同包下进行接口实现,记住大写方法名首字母,这样才是public的。
接下来我们讲下接口的组合,上面讲到接口的组合不被推荐,说法是有问题的,通过后面的学习,接口是可以被组合的,而且在go的源码中还大量被用到,这种组合的方式是很方便的,当然存在的问题也是问题,这个就不去争议了,网上观点不一。
接口的组合上文已经讲过了,这里我们来讲讲interface{},interface{}代表go语言里面的所有类型,意义和java的Object一样,但是go不是说所有类都继承interface,没有这种说法:
i := [...]interface{}{1,"2",3,"a","v"}
 fmt.Println(i)
如上代码,数组元素可以是任意类型,当然可以作为函数传入值类型的时候,就表示可以传入任意类型。
四、               Go两种常用接口。
a)  stringer
这个接口相当与java的toString()方法,我们只要实现string()string方法,就算是实现了这个接口,直接打印对象的时候自动调用string。
func (b B) String() string {
  return "实现了stringer的B"
}
比如这样写。
b)  reader/writer
file其实是实现了reader与writer两个接口的,一般我们需要reader或者writer实例的时候可以传一个file进去也是一样的 ,这里可以用组合
type file interface{
Reader
Writer
}
 
原文地址:https://www.cnblogs.com/luohuayu/p/9197894.html