之前只用过C和C++,对于接口这个概念只存在一个逻辑上的认识,认为它是连接代码写作方和使用方的一个渠道,现在接触到了GO语言,接口(interface{})是GO的一个重要特性,因此决定对C、C++和GO的接口特性进行一番梳理和对比。
C语言
C语言的接口概念较为简单,在只讨论C语言基础用法(不掺杂模拟面向对象的用法)的情况下,其接口与使用方的耦合性最高,如果用户想要调用某块功能代码,通常需要通过包含头文件去获取接口类型。接口使用方不需要关心接口的具体实现,只要在保证接口不变(头文件内的部分)的前提下我们可以自由地修改接口实现代码。C就是一把么得感情的尖刀,他为你提供相当具体的方法,使用方法单一,可扩展性较差(好像有点偏题。。。不过就权当又讨论了一番面向过程和面向对象的区别好了)。举个例子,我们现在需要实现一个使用不同工具去为不同食物剥皮的程序:
#include //接口
typedef struct apple { //苹果
//
const char* name;
int size;
}apple;
typedef struct nut { //坚果
//
const char* name;
int size;
}nut;
typedef struct hammer {
const char* name;
int power; //力量度
int sharpness; //锋利度
}hammer;
typedef struct knife {
const char* name;
int power;
int sharpness;
}knife;
void peelApple_hammer(apple* tar, hammer* tool){
if(!tar || !tool){
return;
}
if (tool->sharpness == 0){
printf("Peel %s by %s Failed.\n", tar->name, tool->name);
return;
}
printf("Peel %s with %s. take %d seconds.\n", tar->name, tool->name, tar->size/tool->sharpness);
return;
}
void peelApple_knife(apple* tar, knife* tool){
if(!tar || !tool){
return;
}
if (tool->sharpness == 0){
printf("Peel %s by %s Failed.\n", tar->name, tool->name);
return;
}
printf("Peel %s with %s. take %d seconds.\n", tar->name, tool->name, tar->size/tool->sharpness);
return;
}
void peelNut_hammer(nut* tar, hammer* tool){
if(!tar || !tool){
return;
}
if (tool->power == 0){
printf("Peel %s by %s Failed.\n", tar->name, tool->name);
return;
}
printf("Peel %s with %s. take %d seconds.\n", tar->name, tool->name, tar->size/tool->power);
return;
}
void peelNut_knife(nut* tar, knife* tool){
if(!tar || !tool){
return;
}
if (tool->power == 0){
printf("Peel %s by %s Failed.\n", tar->name, tool->name);
return;
}
printf("Peel %s with %s. take %d seconds.\n", tar->name, tool->name, tar->size/tool->power);
return;
}
外部程序
int main(){
struct apple strap = {"Apple", 10};
struct nut strnut = {"Nut", 5};
struct hammer strham = {"hammer", 5, 0};
struct knife strkni = {"knife", 1, 5};
peelApple_hammer(&strap, &strham);
peelApple_knife(&strap, &strkni);
peelNut_hammer(&strnut, &strham);
peelNut_knife(&strnut, &strkni);
return 0;
}
打印:
Peel Apple by hammer Failed.
Peel Apple with knife. take 2 seconds.
Peel Nut with hammer. take 1 seconds.
Peel Nut with knife. take 5 seconds.
此时,如果我们想要增加一种水果或一种剥皮工具,就至少需要增加一个数据类型和1*n(当前水果或工具的种类数)个函数接口。
C++
C++的接口可被称之为抽象类,即设定一个抽象基类去描述一组基本行为(接口),然后通过多样化的派生类去完成该接口。接口作为不同组件之间的契约存在,且对契约的实现是强制的,即语言必须声明实现了该接口。
#include class fruit;
接口
class toolsak {
public:
toolsak(const char* na, int pow, int shar){
name = na;
power = pow;
sharpness = shar;
}
const char* name;
int power;
int sharpness;
};
class fruit {
public:
fruit(const char* na, int siz){
name = na;
size = siz;
}
const char* name;
int size;
public:
virtual void peel(toolsak* tool) = 0;
};
class hammer : public toolsak {
public:
hammer(const char* name, int power, int sharp):toolsak(name, power, sharp){}
};
class knife : public toolsak {
public:
knife(const char* name, int power, int sharp):toolsak(name, power, sharp){}
};
class apple : public fruit {
public:
apple(const char* name, int size):fruit(name, size){}
void peel(toolsak* tool){
if(!tool || tool->sharpness == 0){
return;
}
printf("Peel the %s with %s. take %d seconds\n", name, tool->name, size/tool->sharpness);
}
};
class nut : public fruit {
public:
nut(const char* name, int size):fruit(name, size){}
void peel(toolsak* tool){
if(!tool || tool->power == 0){
return;
}
printf("Peel the %s with %s. take %d seconds\n", name, tool->name, size/tool->power);
}
};
外部程序
int main(){
toolsak* tptr = new hammer("hammer", 5, 0);
fruit* fptr = new apple("apple", 10);
toolsak* tptr2 = new knife("knife", 1, 5);
fruit* fptr2 = new nut("nut", 5);
fptr->peel(tptr);
fptr->peel(tptr2);
fptr2->peel(tptr);
fptr2->peel(tptr2);
return 0;
}
打印:
Peel the apple with knife. take 2 seconds
Peel the nut with hammer. take 1 seconds
Peel the nut with knife. take 5 seconds
golang
实现类和抽象接口之间不需要硬性连接(即声明继承或虚函数),只要你的实现类实现了某接口规定的方法,那么该类的使用方就可以实例化该类并赋值给接口,然后可以通过接口直接调用具体方法。
package main
import (
"errors"
"fmt"
)
type tool struct {
name string
power int
sharpness int
}
type fruit struct {
name string
size int
}
type toolIf interface {
getName() string
getPower() int
getSharpness() int
}
type fruitIf interface {
peel(too interface{toolIf}) error
}
type hammer struct {
*tool
}
type knife struct {
*tool
}
type apple struct {
*fruit
}
type nut struct {
*fruit
}
func (a *apple) peel(too interface {toolIf}) error {
if too.getSharpness() == 0 {
return errors.New("xxxx")
}
//do that
fmt.Printf("Peel the %v with %v, take %v seconds\n", a.name, too.getName(), a.size/too.getSharpness())
return nil
}
func (n *nut) peel(too interface{toolIf}) error {
if too.getPower() == 0 {
return errors.New("xxxx")
}
//do that
fmt.Printf("Peel the %v with %v, take %v seconds\n", n.name, too.getName(), n.size/too.getPower())
return nil
}
func (t *tool) getName() string {
return t.name
}
func (t *tool) getPower() int {
return t.power
}
func (t *tool) getSharpness() int {
return t.sharpness
}
func main() {
var (
appFr fruitIf = &apple{&fruit{"apple", 10}}
appNu fruitIf = &nut{&fruit{"nut", 5}}
toolHam = &hammer{&tool{"hammer", 5, 0}}
toolKni = &knife{&tool{"knife", 1, 5}}
)
appFr.peel(toolHam)
appFr.peel(toolKni)
appNu.peel(toolHam)
appNu.peel(toolKni)
return
}
打印:
Peel the apple with knife, take 2 seconds
Peel the nut with hammer, take 1 seconds
Peel the nut with knife, take 5 seconds
tips :需要注意,在GO语言中,使用实例对接口赋值最好用指针而不用对象本身,不然有可能编译报错:因为你可以为某个类型或类型指针去定义方法,如:
type fruitIf interface {
getsize() error
getname() error
}
type apple struct{}
func (a apple) getsize() error {
return nil
}
func (a *apple) getname() error {
return nil
}
func main () {
//var appl apple
var frIf fruitIf = &apple{}
frIf.getsize()
}
如上代码编译可以通过,是因为Go可以根据函数1⃣去自动生成一个新方法:
func (a *apple)getsize() error {
return *a.getsize()
}
显然,GO为我们自动做了封装,反之则不然:
ttype fruitIf interface {
getsize() error
}
type apple struct{}
func (a apple) getsize() error {
return nil
}
func (a *apple) getname() error {
return nil
}
func main () {
var appl apple
var frIf fruitIf = appl
frIf.getsize()
}
这样子是不能编译通过的,是因为GO不能根据对象为你生成以下新方法:
func (a apple)getname()error {
return &a.getname()
}
因为GO函数参数全都是按值传递的,为其做操作不能对外部真实对象造成影响。
总结
接口是一种规范,一个协议,一个抽象出来的方法集合,单纯它本身词语的含义上,上述几种语言并无高下,即使他们存在面向对象和面向过程的区别,但是“可以承受需求变化的接口”就要求编程语言有更合理的接口设计,在这个范畴内,个人认为:虽然C语言的接口最为简单,因其语言层面提供的可扩展性较低,显得更容易理解,可是如果需要扩展接口,那么这将是一个极为痛苦的过程;C++通过继承和虚函数大大提升了接口的可扩展性,代码简洁明了,前提是需要显式声明;Go直接在语言层面支持接口的设计,并且使用接口的过程被极大简化,如果你的某个类完全实现了某接口内的方法集,那么你就相当于实现了该接口,即使二者的定义过程并无关联,这就是所谓的“非侵入式接口”。内中含义需慢慢体会~