专栏首页一Li小麦面向接口编程

面向接口编程

面向接口编程

当我们谈到接口的时候,可能会联想到三样事物:

我们经常说一个库或者模块对外提供了某某API。通过主动暴露的接口来通信,可以隐藏软件系统内部的工作细节。这也是我们最熟悉的第一种接口含义。

第二种接口是一些语言提供的关键字,比如Java的interface。interface关键字可以产生一个完全抽象的类。这个完全抽象的类用来表示一种契约,专门负责建立类与类之间的联系。

第三种接口即是我们谈论的“面向接口编程”中的接口,接口的含义在这里体现得更为抽象。用《设计模式》中的话说就是:接口是对象能响应的请求的集合。

本文讨论第二种和第三种。

JAVA的抽象类

假设java的世界里,有一只鸭子(Duck),还有一只让鸭子发声的(AnimalSound)类,它有一个makeSound方法,接受Duck类型的对象为参数,当让这个鸭子叫的时候,用代码描述就是:

    public class Duck {

      public void makeSound(){

        System.out.println('嘎');

      }

    }


    public class AnimalSound {

      // 只接受Duck类型参数

      public void makeSound(Duck duck){

        duck.makeSound();

      }

    }


    public class Test{

      public static void main(String args[]){

        AnimalSound animalSound=new AnimalSound();

        Duck duck=new Duck();

        animalSound.makeSound(duck); // 输出:嘎

      }

    }

超类实现继承

后来这个世界多了一只鸡,现在我们想让鸡也叫唤起来,但发现这是一件现有代码不可能完成的事情,因为在上面这段代码中AnimalSound类的sound方法里,被规定只能接受Duck类型的对象作为参数。

在享受静态语言类型检查带来的安全性的同时,我们也失去了一些编写代码的自由。

像java这样的静态语言,通常可以设置为"向上传型"——当给一个类变量赋值时,这个变量的类型既可以使用这个类本身,也可以使用这个类的超类。

就像看到鸭子叫,我们既可以说“一只鸭子在叫”,也可以说“一只鸟在叫”,甚至可以说成“一只生物在叫”。通过向上转型,对象的具体类型被隐藏在“超类型”身后。当对象类型之间的耦合关系被解除之后,这些对象才能在类型检查系统的监视下相互替换使用,这样才能看到对象的多态性。

所以如果想让鸡也叫唤起来,必须先把duck对象和chicken对象都向上转型为它们的超类型Animal类,进行向上转型的工具就是抽象类或者interface。

    public abstract class Animal{

      abstract void makeSound(); // 抽象方法

    }


    // 让Duck类和Chicken类都继承自抽象类Animal:

    public class Duck extends Animal {

      public void makeSound(){

        System.out.println('嘎');

      }

    }


    public class Chicken extends Animal {

      public void makeSound(){

        System.out.println('咯');

      }

    }

因此,AnimalSound的makeSound方法需要接受Animal类而非Duck类

    public class AnimalSound {

      // 只接受Duck类型参数

      public void makeSound(Animal animal){

        animal.makeSound();

      }

    }


    public class Test{

      public static void main(String args[]){

        AnimalSound animalSound=new AnimalSound();

        Animal duck=new Duck();

        animalSound.makeSound(duck); // 输出:嘎


        Animal chicken=new Chicken();

        animalSound.makeSound(chicken); // 输出:咯

      }

    }

很明显,Animal是抽象类。它的主要作用在于向上传型,完成多态的功能,更重要的是建立契约——这些契约行为暴露了一个类或者对象能够做什么,但是不关心具体如何去做。

继承自抽象类的具体类都会继承抽象类里的abstract方法,并且要求覆写它们。这些契约在实际编程中非常重要,可以帮助我们编写可靠性更高的代码。比如在上面代码中,各个子类都必须实现makeSound方法,才能保证在不会抛出异常。

总而言之,不关注对象的具体类型,而仅仅针对超类型中的“契约方法”来编写程序,可以产生可靠性高的程序,也可以极大地减少子系统实现之间的相互依赖关系。这就是我们本文要讨论的主题:

面向接口编程,而不是面向实现编程。

从过程上来看,“面向接口编程”其实是“面向超类型编程”。当对象的具体类型被隐藏在超类型身后时,这些对象就可以相互替换使用,我们的关注点才能从对象的类型转移到对象的行为上。

interface

上面的代码中,我们通过引入超类Animal来解决问题。但是有个缺点在于此Animal抽象类是基于单继承的,也就是说我们不可能让Duck和Chicken再继承自另一个家禽类。如果使用interface,可以仅仅针对发出叫声这个行为来编写程序,同时一个类也可以实现多个interface。

    public interface class Animal{

      abstract void makeSound(); // 抽象方法

    }


    // 让Duck类和Chicken类都继承自抽象类Animal:

    public class Duck implements Animal {

      public void makeSound(){

        System.out.println('嘎');

      }

    }


    public class Chicken implements Animal {

      public void makeSound(){

        System.out.println('咯');

      }

    }


    // ...

    // 其它代码同上

回到js

本文花费大量篇幅在java的代码描述上,是为了说明面向接口编程的两个要点:

  • 通过向上转型来隐藏对象的真正类型,以表现对象的多态性。
  • 约定类与类之间的一些契约行为。

本系列文章也多次提到,JavaScript不是一个真正有类的语言。面向接口编程在JavaScript中的最大作用就退化到了检查代码的规范性。比如检查某个对象是否实现了某个方法,或者检查是否给函数传入了预期类型的参数。如果忽略了这两点,有可能会在代码中留下一些隐藏的bug。比如我们尝试执行obj对象的show方法,但是obj对象本身却没有实现这个方法,

Uncaught TypeError:: aaa.show() is not a function.

这是很多jser都会遇到过的错误。为此我们不得不写一些防御性的代码:

aaa.show && aaa.show()

这时候你会想,如果能有静态语言的类型检查机制,我就不用在业务代码中写那么多无关的东西了。

在js中,假设你要判断一个对象是否数组,可能会这么做:

    var isArray=function(obj){

      return obj&&

        typeof obj==='object'&&

        typeof obj.length==='number'&&

        typeof obj.splice==='function'

    };

如果它满足数组的方法,它就可以当数组来用了。现在看一个更加"高级"的语言是如何实现的。

基于TypeScript的命令模式

TypeScript是微软开发的一种编程语言,是JavaScript的一个超集。TypeScript代码最终会被编译成原生的JavaScript代码执行。通过TypeScript,我们可以使用静态语言的方式来编写JavaScript程序。用TypeScript来实现一些设计模式,显得更加原汁原味。本系列文章结束后,笔者也将对ts进行系统梳理。

假设我们正在编写一个用户界面程序,页面中有成百上千个子菜单。因为项目很复杂,我们决定让整个程序都基于命令模式来编写,即编写菜单集合界面的是某个程序员,而负责实现每个子菜单具体功能的工作交给了另外一些程序员。

那些负责实现子菜单功能的程序员,在完成自己的工作之后,会把子菜单封装成一个命令对象,然后把这个命令对象交给编写菜单集合界面的程序员。他们已经约定好,当调用子菜单对象的execute方法时,从而执行对应的子菜单命令。

    // 定义抽象类

    interface Command{

      execute:Function;

    }


    class RefreshMenuBarCommand implements Command{

      constructor(){}

      execute(){

        console.log('刷新菜单界面');

      }

    }


    class AddSubMenuCommand implements Command{

      constructor(){}

      execute(){

        console.log('增加二级菜单');

      }

    }

假设某个程序员一时疏忽,在删除二级菜单方法中漏写了execute:

    class DelSubMenuCommand implement sCommand{

      constructor(){}

      //忘记重写execute方法

    }


    const delSubMenuCommand=new DelSubMenuCommand();


    delSubMenuCommand.execute();


    //输出:Uncaught Type Error:undefined is not a function

ts的编译器给出了报错提示。

本文分享自微信公众号 - 一Li小麦(gh_c88159ec1309),作者:一li小麦

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • “小”程序(1)

    当一个吝啬的甲方提出,想做一款“很简单的”app。那么你可以劝他,不如做个小程序。这种情况下,无需养多两个大前端(ios和安卓),一个微信就能解决绝大多数的适配...

    一粒小麦
  • react的前端验证码

    完整代码: https://github.com/dangjingtao/vccode效果预览

    一粒小麦
  • h5 补白(1)

    它是一种软件,用来制作网页游戏、动画,以及视频播放器。只要观看网络视频,基本都会用到它。

    一粒小麦
  • 单细胞转录组中的pseudotime究竟是什么

    对于单细胞转录组数据,通过聚类分析,我们可以得到细胞亚型,再通过差异分析,可以得到不同细胞亚型的marker基因,结合下游的功能分析,可以让我们对细胞类型和功能...

    生信修炼手册
  • 《Hello NumPy》系列-运算与函数应用

    高阶部分篇篇都是干货,建议大家不要错过任何一节内容,最好关注我,方便看到每次的文章推送。

    知秋小一
  • 聊一聊让我蒙蔽一晚上的各种常量池

    在写之前我们先来看几个问题,假如你对这些问题已经很懂了的话,那大可不用看这篇文章,如果不大懂的话,那么可以看看我的想法。

    帅地
  • TensorFlow风格指南

    此页面包含TensorFlow的开发人员和用户应遵循的风格决策,以增加其代码的可读性,减少错误数量并提高一致性。 Python风格 一般遵循 PEP8 P...

    片刻
  • [ASP.NET Core 3框架揭秘] Options[6]: 扩展与定制

    由于Options模型涉及的核心对象最终都注册为相应的服务,所以从原则上讲这些对象都是可以定制的,下面提供几个这样的实例。由于Options模型提供了针对配置系...

    蒋金楠
  • 浅析PropertySource 基本使用

    一、PropertySource 简介二、@PropertySource与Environment读取配置文件三、@PropertySource与@Value读取...

    cxuan
  • HGE系列之三 渐入佳境

    前两次“乱七八糟”的讲述了一些HGE的基础知识,不知看过的朋友有何感想,反正我自己都觉着有些不知所谓(!),但本着坚持到底的原则,今天继续献上拙文一篇,如果有朋...

    用户2615200

扫码关注云+社区

领取腾讯云代金券