在实习了一个星期之后,对接口有了更深入的思考。
写代码对我而言只是一种兴趣。由于没有任何人对我做出任何要求,很多时候,我只要求将东西写出来就可以了。每当学到新的东西,就一股脑地加入其中,久而久之,我再翻看过去写的代码,只觉得冗长且难看。好在,这些东西大多数时候只有我一个人看,写成什么样都无所谓了。
但现在马上毕业,也不能再任性下去。我得学会和他人合作,共同写出每一个人都能理解的东西。重要的是,当其中一个人离开时,新加入的人可以理解,并很快加入其中。
当然,站在老板的角度上,这样就可以压榨更多员工了。但站在更大的角度上看,我们所做出来的东西,如果能被世界的人理解,并使用,共同创造出一个东西。这一个东西,不会因为时间,空间,乃至人的变化,而变化,它可以被每一个人所继承,所共有,并传递下去,实现永恒,这不是很棒吗?
过去我一直一个人在战斗,虽然也会用大佬做的库,但它们只是我实现目标的工具,并非过程。而这一次,要实现的就是工具。简单来讲,我的职责,从调用者,变成了服务者。既然职责发生了转变,那么思维也要跟着发生转变。
于是,我开始理解,过去无法理解的事情。
问题
JAVA 的面向对象中,有一个叫做接口(Interface)的东西。接口很像一个类,又与类不同,比如,接口禁止申明函数的过程。就比如下面的写法:
interface mouth{
public void sayHello();
}
//下面的写法是禁止的
interface mouth{
public void sayHello(){
return "Hello";
}
}
那函数没有具体的功能,那它就没用了吗?
不,是有用的,它可以被其它类实现 (implements),然后赋予这个函数功能。比如下面的写法:
public class Chinese implements mouth{
public void sayHello(){
return "你好!";
}
}
public class Japenese implements mouth{
public void sayHello(){
return "konijiwa!";
}
}
很奇怪吧。明明在最开始就可以做到的事情,为什么要在后面用另一个类实现? 明明可以用继承的方式,为什么要多加一个接口的概念?
约定
在现实生活中,充斥着大量的约定。比如说,去买早点,大家都约定好,你给我钱,我给你吃的;手机买回来,充电插头一定是双脚的;无论在哪买的 USB,都能插上电脑,传输数据;无论什么城市,过马路时,红灯都得停下来。。。
小到微观世界,大到整个宇宙,大大小小都围绕着某种约定,做着正确的事。一旦违背了约定,定会遭到惩罚。
仔细思考一下约定这种行为,它必定是由多个人共同承担起。假如世界上只有一个人,他做什么事都不会违背约定。
接口其实也是一种约定。用更准确的说法,便是「协议」(protocol)。
嘴巴有个功能就是说话,至于怎么说,说什么,每个人都有不同的实现方式。中国人有中国人的说法,日本人有日本人的说法,但它们都要满足,会说这个功能(这里只是为了方便解释,就不要问我哑巴怎么办)。
在多人合作时,这样就很方便。只要有接口就好了,具体怎么做就看你的了。但无论你怎么做,都不能违返这个接口定下的规则。
服务
当我们尝试将接口变成核心时,就会发现,很多事情变得简单了。
首先,我不再关心调用者实现,调用者也不再关心我的实现。比如有调用者用了我的类做了点事情。
class user{
public function do(Human H){
H.sayHello();
H.sayGoodbye();
}
}
这里的 H,调用者可以放 Chinese 实例,可以放 Japenese 实例,可以放 American 实例,都无所谓。只要他传进来的东西满足
- 是 Human 类
- 实现了统一的接口
那就没任何问题。
与之而来的是,我这边的任务也会很轻松。我定义了接口之后,就可以在满足接口的定义下,任意实现功能。
调用者和服务者之间的联系变弱了,但也更强硬了,我们不一定需要你,但我们都必须遵守约定,遵守接口。
云服务
现在出现的大量的云服务,也是建立在这个基础之上。如果有兴趣,可以上腾讯云或者阿里云的网站上看看。
如果想要人脸识别的功能,你大可不必亲自实现,只要满足了接口的要求,花点小钱,就可以实现。
更深一点的内容
如果只是想了解接口,上面的东西已经足够了。但如果想要来点更深的东西,就继续往下看吧。
依赖注入(Dependency Injection) 依賴注入是我在看 Spring 框架时,遇到的第一个词。
之所以用接口,是因为我们在编程时,不得不面对一个问题:依赖。
在实际工作中,经常就会写出这样的东西。
public BMW_wheel{
public void go{
System.out.print("BMW's wheels working!");
}
}
public BMW_Car {
Wheel BMW_Wheel;
public BWM_Car(){
BMW_Wheel = new BMW_Wheel();
}
public void BMM_Run(){
BMW_Wheel.go();
}
}
public Person{
public void drive(){
BMW_Car Car = new BMW_Car();
Car.BMW_Run();
}
}
上面的代码是没任何问题,事实上,也是可以这么写的。但问题在于,假如我做出的这个东西不是我一个人用,而是分享给很多人用,有的人不想用宝马的轮子,而是想用奔驰的轮子,有的人想改用自己家造的轮子。那么每个人都要读懂上面我写的东西,改得 Car 的实例化过程,最后才能正常的用自己设定的轮子跑起来。如果,假设,在 BMW_Car 又被另一个 Person 类依赖,Person 类被 Company 依赖,那么改动一小点,都会引发滚雪球式的灾难。
而解决这一个问题的思想,就是改变我们的想法,将依赖关系倒转过来。
如果我在写上面的类时,留有一些空隙,问题就会变得很简单。
interface Car{
public void Run(Wheel wheel);
}
同时,轮子类也做个接口
interface Wheel{
public void go();
}
之后,实现车类。
public BMW_Car implement Car{
Wheel wheel;
public void BMW_Car(Wheel wheel){
this.wheel = wheel;
}
public void Run(){
this.wheel.go();
}
}
public Tesla implements Car{
Wheel wheel;
public void Tesla(Wheel wheel){
this.wheel = wheel;
}
public void Run(){
this.wheel.go();
}
}
通过接口,轮子类和车轮之间的耦合度降低了。在上面,我不再关心这辆宝马车用的是什么轮子。我要的只是轮子,你只要满足轮子接口的东西就可以了。
你完全可以写成下面的代码。
class Person{
public void driving(){
Car BMW_Car = new BMW_Car()
}
}
(为什么不用万能的 Python 来写这段代码?那是因为 Python 是鸭子类型啊,不需要接口)
REST 风格
解决依赖的方法除了在代码上,我们还可以通过使用更成熟的思想。
在 2000 年,Fielding 大佬的博士论文的第五章,介绍了一种架构风格,叫作 REST(Representation state transfer)。这种风格,如今应用于各大应用中。
简单来说,客户端与服务器交流时,每次请求都与上一次无关。换而言之,每一次请求都不依赖于上一次请求。这样,我们将耦合降到了最低。当我需要资源时,只需要向服务器请求,我只知道向哪一个 URL 请求,根本无需知道我所请求的是分布式系统中的具体的哪一个,因为每一次请求对它们而言,都是崭新的。这样,不需要担心可拓展性。
同时,RESTful 带来的好处还有:改善了可见性。由于每一次请求得到的数据都不应该超过一次请求所得到的数据,因此数据的完整性得到了保证。同时,提升了可靠性。如果请求出了问题,我们也只需要恢复一次请求便可以了。
通过这一风格设计出来的接口,在通用性上会大大增强。
MASHUP 应用
当接口设计得足够多,与之而来的,应用上的发展也开始了。
mashup 最早起源于音乐界,将不同的音乐混合在一起的演出方式。在如今的 IT 界中,它表示一种应用做法:组合不同的 WEB 服务,合成一个应用。
举个例子。 2018年,GOOGLE 公开了 GOOGLE MAP 的数据接口。因此,每个人都可以用它,和其它服务,比如天气服务接口,做出一个新的应用,在地图上显示每个城市的天气,或者做更有趣的,比如抄袭 Pokemon GO,做个捉妖怪的游戏。
mashup 的组成分成三部分。一部分是服务提供商(者),一部分是 mashup 站点,一部分是客户端。这三者每一部分都是可替代,我可以用不同服务商提供的 api,这个应用也可是另一个人来做,至于服务端,大多数是基于浏览器,你换成哪一个浏览器都可以。于是,mashup 属于松散耦合,可以说,是利用接口最好的诠释。