第
通过与Java功能上的对比来学习Go语言
foreach($this-varsas$name=$value){
$template=str_replace({.$name.},$value,$template);
return$template;
}
这个时候,如果有另外有一个接口??iTemplate2??声明了与??iTemplate??完全一样的接口方法,甚至名字也叫??iTemplate??,只不过位于不同的命名空间下,编译器也会认为上面的类??Template??只实现了??iTemplate??而没有实现??iTemplate2??接口。
这在我们之前的认知中是理所当然的,无论是类与类之间的继承,还是类与接口之间的实现,在Java、PHP这种单继承语言中,存在着严格的层级关系,一个类只能直接继承自一个父类,一个类也只能实现指定的接口,如果没有显式声明继承自某个父类或者实现某个接口,那么这个类就与该父类或者该接口没有任何关系。
我们把这种接口称为侵入式接口,所谓「侵入式」指的是实现类必须明确声明自己实现了某个接口。这种实现方式虽然足够明确和简单明了,但也存在一些问题,尤其是在设计标准库的时候,因为标准库必然涉及到接口设计,接口的需求方是业务实现类,只有具体编写业务实现类的时候才知道需要定义哪些方法,而在此之前,标准库的接口就已经设计好了,我们要么按照约定好的接口进行实现,如果没有合适的接口需要自己去设计,这里的问题就是接口的设计和业务的实现是分离的,接口的设计者并不能总是预判到业务方要实现哪些功能,这就造成了设计与实现的脱节。
接口的过分设计会导致某些声明的方法实现类完全不需要,如果设计的太简单又会导致无法满足业务的需求,这确实是一个问题,而且脱离了用户使用场景讨论这些并没有意义,以PHP自带的??SessionHandlerInterface??接口为例,该接口声明的接口方法如下:
SessionHandlerInterface{
/*方法*/
abstractpublicclose(void):bool
abstractpublicdestroy(string$session_id):bool
abstractpublicgc(int$maxlifetime):int
abstractpublicopen(string$save_path,string$session_name):bool
abstractpublicread(string$session_id):string
abstractpublicwrite(string$session_id,string$session_data):bool
}
用户自定义的Session管理器需要实现该接口,也就是要实现该接口声明的所有方法,但是实际在做业务开发的时候,某些方法其实并不需要实现,比如如果我们基于Redis或Memcached作为Session存储器的话,它们自身就包含了过期回收机制,所以??gc??方法根本不需要实现,又比如??close??方法对于大部分驱动来说,也是没有什么意义的。
正是因为这种不合理的设计,所以在编写PHP类库中的每个接口时都需要纠结以下两个问题(Java也类似):
一个接口需要声明哪些接口方法?如果多个类实现了相同的接口方法,应该如何设计接口?比如上面这个??SessionHandlerInterface??,有没有必要拆分成多个更细分的接口,以适应不同实现类的需要?
接下我们来看看Go语言的接口是如何避免这些问题的。
2.2.2Go语言的接口实现
在Go语言中,类对接口的实现和子类对父类的继承一样,并没有提供类似??implement??这种关键字显式声明该类实现了哪个接口,一个类只要实现了某个接口要求的所有方法,我们就说这个类实现了该接口。
例如,我们定义了一个??File??类,并实现了??Read()??、??Write()??、??Seek()??、??Close()??四个方法:
typeFilestruct{
//...
func(f*File)Read(buf[]byte)(nint,errerror)
func(f*File)Write(buf[]byte)(nint,errerror)
func(f*File)Seek(offint64,whenceint)(posint64,