一、接口的应用
在前面的文章中阐述了接口的内涵和本质,并举了初步的例子进行说明。但在实际的应用中,接口使用的非常频繁和广泛。可以这样说,只要是一个可用的程序,到处都有接口的身影。而在实际的应用中,接口并不会如简单的看到的例子那样简单。它可能是一个明白的函数,也可能被隐藏在各种的服务中。下面将会就在实际中常见到几类接口的实现方法和手段分别进行说明。
二、接口实现的方式
接口的实现方式和技术有很多,但可以按照发展的历史来分别说明可能更容易理解。早期的编程开发其实就是单机开发,网络应用开发流行起来是后来的事。
1、单机
单机开发中,接口实现在不同的角度下看又可以分成:
a) 进程内
进程内一般是使用函数(包括各种类似函数的实现如Lambda表达式、仿函数以及函数指针等)做为接口,至于是什么函数,理论上是都可以的。就看具体的应用场景,比如在单例中可能就是一个静态函数,在库中是一个全局函数,在模块间可能是一个普通的函数等。当然,这其中回调函数是一个绕不开的话题。
b) 跨进程
跨进程的通信一般是使用消息、事件和MQ,还有大家经常使用到的RPC。后来出现的WebService也可以划到这个中来。当然,使用一些OS的系统接口(共享内存)或网络接口等实现也都可以划到这其中来。
2、分布式
其实谈到接口的实现,在互联网或者移动互联网中,更能体现接口的复杂性,只不过本文不展开讨论罢了。在分布式开发中,接口的应用非常多,主流的有:
a) 网络通信接口,如常见的Socket等。它的内部可能就包含上面的回调函数等的应用
b) 服务接口如WebService、RPC等。它的内部实现可能就包括事件、网络接口、内部服务等的应用
c) 消息和中间件接口(MQ等)等。这个就更复杂了,可能包含上面所有的接口实现
d) HTTP和Restful服务(包括更宏观层面上的微服务等)。它其实是架设在网络通信接口之上的应用,由于传递的链路很长,所以内部实现可能大不一样
e) 其它。这个也比较多,包括一些不为人注意的如蓝牙通信接口,在一些物联网上应用可能就比较多。
这里讨论的是接口的实现形式和技术,所以请区别开前一篇的接口的相关内容。虽然不管哪种情况其实最终都要落在函数头上,但一如好的厨子,都是面粉,可能做出来的面点完全不同。这样大家可能会有一个更直观的区别理解。
三、实例
在分析说明了上面的接口实现的技术和方式后,下面看几个相关的例子:
//Restful
//需要安装httplib
// 处理 JSON 响应
void ResponseSet(Response& res, const json& data, int status = 200) {res.set_content(data.dump(), "application/json");res.status = status;
}int main() {Server svr;// GET--all usersvr.Get("/api/users", [](const Request& req, Response& res) {json response_data;......ResponseSet(res, response_data);});// POST-create usersvr.Post("/api/users", [](const Request& req, Response& res) {try {auto user_data = json::parse(req.body);// checkif (!user_data.contains("name") || !user_data.contains("phone")) {ResponseSet(res, {{"error", "pars missing !"}}, 400);return;}......set_json_response(res, newUser, 201); // 201 Created} catch (const json::exception& e) {set_json_response(res, {{"error", e.what()}}, 400);}});std::cout << "start server: http://localhost:8080\n";svr.listen("0.0.0.0", 8080);return 0;
}
再看一个RPC的例程:
//需要rpclib
//server// RPC func
int Add(int a, int b) {return a + b;
}int main() {rpc::server srv(8080); // registersrv.bind("add", &Add);std::cout << "RPC Server start..." << std::endl;srv.run(); return 0;
}
//client
int main() {rpc::client client("127.0.0.1", 8080);// call Addauto result = client.call("add", 3, 4);std::cout << "3 + 4 = " << result_add.as<int>() << std::endl;return 0;
}
其它简单的方法就不再赘述,已经写过很多次了。
四、实现方式的对比
一般来说,凡是抽象层次高的,应用起来相对就不直观,易用性就差一些,而且可能考虑的东西就多一些。但事物的多样性决定了其复杂的相对性,大家不要过度纠结于实现方式的手段,按需应用即可。下面从几个方面对上面的实现方式进行对比:
1、便捷易用性
使用函数一般来说是最简单的,但它的应用更多局限于单机之间。而WebService和RPC相对复杂,但可以跨进程跨平台,支持分布式的部署。而MQ中间件等就相当复杂了,而且它一般不会在单机甚至小的分布式系统上应用。
HTTP和Restful其实是一种网络服务的接口,它的应用也非常复杂。可能一个简单的服务接口,需要不少的后台技术支撑。当然,现在有不少的语言和框架支持快速部署,这是另外一回事。封装的结果当然是要让代码更好用。
2、效率
效率上直接使用函数肯定是效率最好的,一个指针的操作对计算机来说几乎不耗费时间;而WebService和RPC耗费的时间会更长;而HTTP和RESTful在这些实现方式中大约居中的情况;MQ和中间件基本上HTTP等差不多。而事件机制就看底层实现的逻辑了,使用哪种逻辑,可能就会更倾向于哪种方式的效率。
3、实现的难度和复杂度
一般来说除了直接应用的函数方式的接口容易实现,没有什么难度和复杂度外;像MQ,HTTP等都因为抽象层次的限制,实现起来有相当的难度。换句话说,小的项目基本就不要使用这些技术,中型项目控制使用,大型项目谨慎使用。当然,还有系统级和框架级的项目,这个就看开发者的能力和应用的场景要求了。
4、可扩展性和可维护性
一般来说,抽象的层次越高,可扩展性就越强。否则,为什么要抽象?但抽象的复杂性使得可维护性会有所降低,但抽象与可维护性不是一条线性的关系。好的抽象会达到一个可扩展性和可维护性的平衡。说到这里,可能就不需要再说哪个方式和技术在扩展性和可维护性上的高低了吧。
五、总结
爱看武侠小说的知道有形意拳这个拳法,形和意,一外一内。而在武林中又有一句话“练拳不练功,到老一场空;练功不练拳,到老也枉然(犹如无舵船)!”。其实编程也是如此,光明白理论,不进行实践,那么进步就无从谈起;反之,光进行实践不学习理论,则方向无法把握,事倍功半。
前面反复提及,计算机编程是一个理论和实践高度结合的技术,理论和实践是互相促进互相发展的,请大家务必明白这一点。既要扎实的推进编程的水平,又要不断的多看一些英文的资料紧跟技术发展的潮流和方向。