zoukankan      html  css  js  c++  java
  • 接口和面向接口编程

    当我们谈到接口的时候,通常会涉及以下几种含义,下面先简单介绍。
    我们经常说一个库或者模块对外提供了某某 API接口。通过主动暴露的接口来通信,可以隐
    藏软件系统内部的工作细节。这也是我们最熟悉的第一种接口含义。
    第二种接口是一些语言提供的关键字,比如 Java的 interface 。 interface 关键字可以产生一
    个完全抽象的类。这个完全抽象的类用来表示一种契约,专门负责建立类与类之间的联系。
    第三种接口即是我们谈论的“面向接口编程”中的接口,接口的含义在这里体现得更为抽象。
    用《设计模式》中的话说就是:
    接口是对象能响应的请求的集合。
    本章主要讨论的是第二种和第三种接口。首先要讲清楚的是,本章的前半部分都是针对 Java
    语言的讲解,这是因为 JavaScript 并没有从语言层面提供对抽象类( Abstract class )或者接口
    ( interface )的支持,我们有必要从一门提供了抽象类和接口的语言开始,逐步了解“面向接口
    编程”在面向对象程序设计中的作用。

    JavaScript 语言是否需要抽象类和 interface;

    在java中的抽象类和 interface 的作用主要都是以下两点。
     通过向上转型来隐藏对象的真正类型,以表现对象的多态性。
     约定类与类之间的一些契约行为。
    对于 JavaScript而言,因为 JavaScript是一门动态类型语言,类型本身在 JavaScript中是一个
    相对模糊的概念。也就是说,不需要利用抽象类或者 interface 给对象进行“向上转型”。除了
    number 、 string 、boolean等基本数据类型之外,其他的对象都可以被看成“天生”被“向上转型”成了 Object 类型:

    var ary = new Array();
    var date = new Date();
    如果 JavaScript是一门静态类型语言,上面的代码也许可以理解为:
    Array ary = new Array();
    Date date = new Date();
    或者:
    Object ary = new Array();
    

    因为不需要进行向上转型,接口在 JavaScript中的最大作用就退化到了检查代码的规范性。
    比如检查某个对象是否实现了某个方法,或者检查是否给函数传入了预期类型的参数。如果忽略
    了这两点,有可能会在代码中留下一些隐藏的 bug。比如我们尝试执行 obj 对象的 show 方法,但
    是 obj 对象本身却没有实现这个方法,代码如下:

    function show( obj ){
    obj.show(); // Uncaught TypeError: undefined is not a function
    }
    var myObject = {}; // myObject 对象没有 show 方法
    show( myObject );
    或者:
    function show( obj ){
    obj.show(); // TypeError: number is not a function
    }
    var myObject = { // myObject.show 不是 Function 类型
    show: 1
    };
    show( myObject );
    此时,我们不得不加上一些防御性代码:
    function show( obj ){
    if ( obj && typeof obj.show === 'function' ){
    obj.show();
    }
    }
    或者:
    function show( obj ){
    try{
    obj.show();
    }catch( e ){
    }
    }
    var myObject = {}; // myObject 对象没有 show 方法
    // var myObject = { // myObject.show 不是 Function 类型
    // show: 1
    // }    ;
    show( myObject );
    

    如果 JavaScript有编译器帮我们检查代码的规范性,那事情要比现在美好得多,我们不用在业
    务代码中到处插入一些跟业务逻辑无关的防御性代码。 作为一门解释执行的动态类型语言,把希
    望寄托在编译器上是不可能了。如果要处理这类异常情况,我们只有手动编写一些接口检查的代码。

    用鸭子类型进行接口检查

    “如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。
    鸭子类型是动态类型语言面向对象设计中的一个重要概念。利用鸭子类型的思想,不必借助
    超类型的帮助,就能在动态类型语言中轻松地实现本章提到的设计原则:面向接口编程,而不是
    面向实现编程。比如,一个对象如果有 push 和 pop 方法,并且提供了正确的实现,它就能被当作
    栈来使用;一个对象如果有 length 属性,也可以依照下标来存取属性,这个对象就可以被当作
    数组来使用。如果两个对象拥有相同的方法,则有很大的可能性它们可以被相互替换使用。

    在 Object.prototype.toString.call( [] ) === '[object Array]' 被发现之前,我们经常用鸭子
    类型的思想来判断一个对象是否是一个数组,代码如下:

    var isArray = function( obj ){
    return obj &&
    typeof obj === 'object' &&
        typeof obj.length === 'number' &&
        typeof obj.splice === 'function'
        };
    

    当然在 JavaScript开发中,总是进行接口检查是不明智的,也是没有必要的,毕竟现在还找
    不到一种好用并且通用的方式来模拟接口检查,跟业务逻辑无关的接口检查也会让很多
    JavaScript程序员觉得不值得和不习惯。在Ross Harmes和Dustin Diaz合著的Pro JavaScript Design
    Pattrns一书中,提供了一种根据鸭子类型思想模拟接口检查的方法,但这种基于双重循环的检查
    方法并不是很实用,而且只能检查对象的某个属性是否属于 Function 类型。

    用 TypeScript 编写基于 interface 的命令模式

    虽然在大多数时候 interface 给 JavaScript开发带来的价值并不像在静态类型语言中那么大,
    但如果我们正在编写一个复杂的应用,还是会经常怀念接口的帮助。
    下面我们以基于命令模式的示例来说明 interface 如何规范程序员的代码编写,这段代码本
    身并没有什么实用价值,在 JavaScript中,我们一般用闭包和高阶函数来实现命令模式。
    假设我们正在编写一个用户界面程序,页面中有成百上千个子菜单。因为项目很复杂,我们
    决定让整个程序都基于命令模式来编写,即编写菜单集合界面的是某个程序员,而负责实现每个
    子菜单具体功能的工作交给了另外一些程序员。
    那些负责实现子菜单功能的程序员,在完成自己的工作之后,会把子菜单封装成一个命令对
    象,然后把这个命令对象交给编写菜单集合界面的程序员。他们已经约定好,当调用子菜单对象
    的 execute 方法时,会执行对应的子菜单命令。
    虽然在开发文档中详细注明了每个子菜单对象都必须有自己的 execute 方法,但还是有一个
    粗心的 JavaScript 程序员忘记给他负责的子菜单对象实现 execute 方法,于是当执行这个命令的
    时候,便会报出错误,代码如下:

    <html>
    <body>
    <button id="exeCommand">执行菜单命令</button>
    <script>
    var RefreshMenuBarCommand = function(){};
    RefreshMenuBarCommand.prototype.execute = function(){
    console.log( '刷新菜单界面' );
    };
    var AddSubMenuCommand = function(){};
    AddSubMenuCommand.prototype.execute = function(){
    console.log( '增加子菜单' );
    };
    var DelSubMenuCommand = function(){};
    /*****没有实现 DelSubMenuCommand.prototype.execute *****/
    // DelSubMenuCommand.prototype.execute = function(){
    // };
    var refreshMenuBarCommand = new RefreshMenuBarCommand(),
    addSubMenuCommand = new AddSubMenuCommand(),
    delSubMenuCommand = new DelSubMenuCommand();
    var setCommand = function( command ){
    document.getElementById( 'exeCommand' ).onclick = function(){
    command.execute();
        }
    };
    setCommand( refreshMenuBarCommand );
    // 点击按钮后输出:"刷新菜单界面"
    setCommand( addSubMenuCommand );
    //     点击按钮后输出:"增加子菜单"
    setCommand( delSubMenuCommand );
    // 点击按钮后报错。Uncaught TypeError: undefined is not a function
    </script>
    </body>
    </html>
    

    为了防止粗心的程序员忘记给某个子命令对象实现 execute 方法,我们只能在高层函数里添
    加一些防御性的代码,这样当程序在最终被执行的时候,有可能抛出异常来提醒我们,代码如下:

    var setCommand = function( command ){
    document.getElementById( 'exeCommand' ).onclick = function(){
    if ( typeof command.execute !== 'function' ){
    throw new Error( "command 对象必须实现 execute 方法" );
            }
    command.execute();
        }
    }
    

    如果确实不喜欢重复编写这些防御性代码,我们还可以尝试使用TypeScript来编写这个程序。
    TypeScript 是微软开发的一种编程语言,是 JavaScript 的一个超集。跟 CoffeeScript 类似,
    TypeScript代码最终会被编译成原生的 JavaScript代码执行。通过 TypeScript,我们可以使用静态
    语言的方式来编写 JavaScript程序。用 TypeScript来实现一些设计模式,显得更加原汁原味。
    TypeScript目前的版本还没有提供对抽象类的支持,但是提供了 interface 。下面我们就来编
    写一个 TypeScript版本的命令模式。

    interface Command{
    execute: Function;
    }
    

    接下来定义 RefreshMenuBarCommand 、 AddSubMenuCommand 和 DelSubMenuCommand 这 3个类,它们
    分别都实现了 Command 接口,这可以保证它们都拥有 execute 方法:

    class RefreshMenuBarCommand implements Command{
    constructor (){
        }
    execute(){
    console.log( '刷新菜单界面' );
        }
    }
    class AddSubMenuCommand implements Command{
    constructor (){
        }
    execute(){
    console.log( '增加子菜单' );
        }
    }
    class DelSubMenuCommand implements Command{
    constructor (){
    }
    // 忘记重写 execute 方法
    }
    var refreshMenuBarCommand = new RefreshMenuBarCommand(),
    addSubMenuCommand = new AddSubMenuCommand(),
    delSubMenuCommand = new DelSubMenuCommand();
    refreshMenuBarCommand.execute(); // 输出:刷新菜单界面
    addSubMenuCommand.execute(); // 输出:增加子菜单
    delSubMenuCommand.execute(); // 输出:Uncaught TypeError: undefined is not a function
    

    当我们忘记在 DelSubMenuCommand 类中重写 execute 方法时,TypeScript提供
    的编译器会及时给出错误提示。

    这段 TypeScript代码翻译过来的 JavaScript代码如下:
    var RefreshMenuBarCommand = (function () {
    function RefreshMenuBarCommand() {
    }
    RefreshMenuBarCommand.prototype.execute = function () {
    console.log('刷新菜单界面');
    };
    return RefreshMenuBarCommand;
    })();
    var AddSubMenuCommand = (function () {
    function AddSubMenuCommand() {
    }
    AddSubMenuCommand.prototype.execute = function () {
    console.log('增加子菜单');
    };
    return AddSubMenuCommand;
    })();
    var DelSubMenuCommand = (function () {
    function DelSubMenuCommand() {
    }
    return DelSubMenuCommand;
    })();
    var refreshMenuBarCommand = new RefreshMenuBarCommand(),
    addSubMenuCommand = new AddSubMenuCommand(),
    delSubMenuCommand = new DelSubMenuCommand();
    refreshMenuBarCommand.execute();
    addSubMenuCommand.execute();
    delSubMenuCommand.execute();
  • 相关阅读:
    MongoDB查询语句 (增、删、改、查)
    MongoDB简单查询语句
    jquery Select Change事件
    c# 远程监控(4) 接收端 RTP包重组 分屏显示
    c# 远程监控(3) RTP协议 RTP.NET.DLL
    c# 远程监控(1) 大纲
    c# 远程监控(2) 摄像头调研及模拟
    TortoiseGit记住用户名和密码
    winform ListView和DataGridView实现分页
    制作符合平台的CodeSmith代码生产模版
  • 原文地址:https://www.cnblogs.com/koujinshidui/p/7678114.html
Copyright © 2011-2022 走看看