之前只用过C和C++,对于接口这个概念只存在一个逻辑上的认识,认为它是连接代码写作方和使用方的一个渠道,现在接触到了GO语言,接口(interface{})是GO的一个重要特性,因此决定对C、C++和GO的接口特性进行一番梳理和对比。
C语言
C语言的接口概念较为简单,在只讨论C语言基础用法(不掺杂模拟面向对象的用法)的情况下,其接口与使用方的耦合性最高,如果用户想要调用某块功能代码,通常需要通过包含头文件去获取接口类型。接口使用方不需要关心接口的具体实现,只要在保证接口不变(头文件内的部分)的前提下我们可以自由地修改接口实现代码。C就是一把么得感情的尖刀,他为你提供相当具体的方法,使用方法单一,可扩展性较差(好像有点偏题。。。不过就权当又讨论了一番面向过程和面向对象的区别好了)。举个例子,我们现在需要实现一个使用不同工具去为不同食物剥皮的程序:
#include <stdio.h>//////接口
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 <stdio.h>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 mainimport ("errors""fmt"
)type tool struct {name stringpower intsharpness int
}type fruit struct {name stringsize int
}type toolIf interface {getName() stringgetPower() intgetSharpness() 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 thatfmt.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 thatfmt.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() errorgetname() error
}type apple struct{}func (a apple) getsize() error {return nil
}func (a *apple) getname() error {return nil
}func main () {//var appl applevar 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 applevar frIf fruitIf = applfrIf.getsize()
}
这样子是不能编译通过的,是因为GO不能根据对象为你生成以下新方法:
func (a apple)getname()error {return &a.getname()
}
因为GO函数参数全都是按值传递的,为其做操作不能对外部真实对象造成影响。
总结
接口是一种规范,一个协议,一个抽象出来的方法集合,单纯它本身词语的含义上,上述几种语言并无高下,即使他们存在面向对象和面向过程的区别,但是“可以承受需求变化的接口”就要求编程语言有更合理的接口设计,在这个范畴内,个人认为:虽然C语言的接口最为简单,因其语言层面提供的可扩展性较低,显得更容易理解,可是如果需要扩展接口,那么这将是一个极为痛苦的过程;C++通过继承和虚函数大大提升了接口的可扩展性,代码简洁明了,前提是需要显式声明;Go直接在语言层面支持接口的设计,并且使用接口的过程被极大简化,如果你的某个类完全实现了某接口内的方法集,那么你就相当于实现了该接口,即使二者的定义过程并无关联,这就是所谓的“非侵入式接口”。内中含义需慢慢体会~