面向接口

在实习了一个星期之后,对接口有了更深入的思考。

写代码对我而言只是一种兴趣。由于没有任何人对我做出任何要求,很多时候,我只要求将东西写出来就可以了。每当学到新的东西,就一股脑地加入其中,久而久之,我再翻看过去写的代码,只觉得冗长且难看。好在,这些东西大多数时候只有我一个人看,写成什么样都无所谓了。

但现在马上毕业,也不能再任性下去。我得学会和他人合作,共同写出每一个人都能理解的东西。重要的是,当其中一个人离开时,新加入的人可以理解,并很快加入其中。

当然,站在老板的角度上,这样就可以压榨更多员工了。但站在更大的角度上看,我们所做出来的东西,如果能被世界的人理解,并使用,共同创造出一个东西。这一个东西,不会因为时间,空间,乃至人的变化,而变化,它可以被每一个人所继承,所共有,并传递下去,实现永恒,这不是很棒吗?

过去我一直一个人在战斗,虽然也会用大佬做的库,但它们只是我实现目标的工具,并非过程。而这一次,要实现的就是工具。简单来讲,我的职责,从调用者,变成了服务者。既然职责发生了转变,那么思维也要跟着发生转变。

于是,我开始理解,过去无法理解的事情。

问题

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 属于松散耦合,可以说,是利用接口最好的诠释。