深入理解JavaScript系列(21):S.O.L.I.D五大原则之接口隔离原则ISP详解


Posted in Javascript onMarch 05, 2015

前言

本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第4篇,接口隔离原则ISP(The Interface Segregation Principle)。

英文原文:http://freshbrewedcode.com/derekgreer/2012/01/08/solid-javascript-the-interface-segregation-principle/
注:这篇文章作者写得比较绕口,所以大叔理解得也比较郁闷,凑合着看吧,别深陷进去了
接口隔离原则的描述是:

Clients should not be forced to depend on methods they do not use.

不应该强迫客户依赖于它们不用的方法。

当用户依赖的接口方法即便只被别的用户使用而自己不用,那它也得实现这些接口,换而言之,一个用户依赖了未使用但被其他用户使用的接口,当其他用户修改该接口时,依赖该接口的所有用户都将受到影响。这显然违反了开闭原则,也不是我们所期望的。

接口隔离原则ISP和单一职责有点类似,都是用于聚集功能职责的,实际上ISP可以被理解才具有单一职责的程序转化到一个具有公共接口的对象。

JavaScript接口

JavaScript下我们改如何遵守这个原则呢?毕竟JavaScript没有接口的特性,如果接口就是我们所想的通过某种语言提供的抽象类型来建立contract和解耦的话,那可以说还行,不过JavaScript有另外一种形式的接口。在Design Patterns ? Elements of Reusable Object-Oriented Software一书中我们找到了接口的定义:
http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

一个对象声明的任意一个操作都包含一个操作名称,参数对象和操作的返回值。我们称之为操作符的签名(signature)。
一个对象里声明的所有的操作被称为这个对象的接口(interface)。一个对象的接口描绘了所有发生在这个对象上的请求信息。
不管一种语言是否提供一个单独的构造来表示接口,所有的对象都有一个由该对象所有属性和方法组成的隐式接口。参考如下代码:

var exampleBinder = {};

exampleBinder.modelObserver = (function() {

    /* 私有变量 */

    return {

        observe: function(model) {

            /* 代码 */

            return newModel;

        },

        onChange: function(callback) {

            /* 代码 */

        }

    }

})();
exampleBinder.viewAdaptor = (function() {

    /* 私有变量 */

    return {

        bind: function(model) {

            /* 代码 */

        }

    }

})();
exampleBinder.bind = function(model) {

    /* 私有变量 */

    exampleBinder.modelObserver.onChange(/* 回调callback */);

    var om = exampleBinder.modelObserver.observe(model);

    exampleBinder.viewAdaptor.bind(om);

    return om;

};

上面的exampleBinder类库实现的功能是双向绑定。该类库暴露的公共接口是bind方法,其中bind里用到的关于change通知和view交互的功能分别是由单独的对象modelObserver和viewAdaptor来实现的,这些对象从某种意义上来说就是公共接口bind方法的具体实现。

尽管JavaScript没有提供接口类型来支持对象的contract,但该对象的隐式接口依然能当做一个contract提供给程序用户。

ISP与JavaScript

我们下面讨论的一些小节是JavaScript里关于违反接口隔离原则的影响。正如上面看到的,JavaScript程序里实现接口隔离原则虽然可惜,但是不像静态类型语言那样强大,JavaScript的语言特性有时候会使得所谓的接口搞得有点不粘性。

堕落的实现

在静态类型语言语言里,导致违反ISP原则的一个原因是堕落的实现。在Java和C#里所有的接口里定义的方法都必须实现,如果你只需要其中几个方法,那其他的方法也必须实现(可以通过空实现或者抛异常的方式)。在JavaScript里,如果只需要一个对象里的某一些接口的话,他也解决不了堕落实现这个问题,虽然不用强制实现上面的接口。但是这种实现依然违反了里氏替换原则。

var rectangle = {

    area: function() {

        /* 代码 */

    },

    draw: function() {

        /* 代码 */

    }

};
var geometryApplication = {

    getLargestRectangle: function(rectangles) {

        /* 代码 */

    }

};
var drawingApplication = {

    drawRectangles: function(rectangles) {

       /* 代码 */

    }

};

当一个rectangle替代品为了满足新对象geometryApplication的getLargestRectangle 的时候,它仅仅需要rectangle的area()方法,但它却违反了LSP(因为他根本用不到其中drawRectangles方法才能用到的draw方法)。

静态耦合

静态类型语言里的另外一个导致违反ISP的原因是静态耦合,在静态类型语言里,接口在一个松耦合设计程序里扮演了重大角色。不管是在动态语言还是在静态语言,有时候一个对象都可能需要在多个客户端用户进行通信(比如共享状态),对静态类型语言,最好的解决方案是使用Role Interfaces,它允许用户和该对象进行交互(而该对象可能需要在多个角色)作为它的实现来对用户和无关的行为进行解耦。在JavaScript里就没有这种问题了,因为对象都被动态语言所特有的优点进行解耦了。

语义耦合

导致违反ISP的一个通用原因,动态语言和静态类型语言都有,那就是语义耦合,所谓语义耦合就是互相依赖,也就是一个对象的行为依赖于另外一个对象,那就意味着,如果一个用户改变了其中一个行为,很有可能会影响另外一个使用用户。这也违反单一职责原则了。可以通过继承和对象替代来解决这个问题。

可扩展性

另外一个导致问题的原因是关于可扩展性,很多人在举例的时候都会举关于callback的例子用来展示可扩展性(比如ajax里成功以后的回调设置)。如果想这样的接口需要一个实现并且这个实现的对象里有很多熟悉或方法的话,ISP就会变得很重要了,也就是说当一个接口interface变成了一个需求实现很多方法的时候,他的实现将会变得异常复杂,而且有可能导致这些接口承担一个没有粘性的职责,这就是我们经常提到的胖接口。

总结

JavaScript里的动态语言特性,使得我们实现非粘性接口的影响力比静态类型语言小,但接口隔离原则在JavaScript程序设计模式里依然有它发挥作用的地方。

Javascript 相关文章推荐
Jquery异步请求数据实例代码
Dec 28 Javascript
键盘KeyCode值列表汇总
Nov 26 Javascript
基于Bootstrap+jQuery.validate实现Form表单验证
Dec 16 Javascript
Jquery ajax基础教程
Nov 20 Javascript
详解XMLHttpRequest(一)同步请求和异步请求
Sep 14 Javascript
Vuejs 组件——props数据传递的实例代码
Mar 07 Javascript
AngularJS ng-repeat指令及Ajax的应用实例分析
Jul 06 Javascript
详谈DOM简介及节点、属性、查找节点的方法
Nov 16 Javascript
JavaScript设计模式之调停者模式实例详解
Feb 03 Javascript
JS实现水平遍历和嵌套递归操作示例
Aug 15 Javascript
vuex存储token示例
Nov 11 Javascript
关于对TypeScript泛型参数的默认值理解
Jul 15 Javascript
深入理解JavaScript系列(19):求值策略(Evaluation strategy)详解
Mar 05 #Javascript
如何实现chrome浏览器关闭页面时弹出“确定要离开此面吗?”
Mar 05 #Javascript
深入理解JavaScript系列(18):面向对象编程之ECMAScript实现
Mar 05 #Javascript
基于zepto.js实现仿手机QQ空间的大图查看组件ImageView.js详解
Mar 05 #Javascript
基于jQuery实现网页进度显示插件
Mar 04 #Javascript
基于jQuery实现仿淘宝套餐选择插件
Mar 04 #Javascript
js实现类似于add(1)(2)(3)调用方式的方法
Mar 04 #Javascript
You might like
点评山进PR-D3L三波段收音机
2021/03/02 无线电
PHP 5.0对象模型深度探索之绑定
2006/09/05 PHP
PHP垃圾回收机制简单说明
2010/07/22 PHP
PHP把小数转成整数3种方法
2014/06/30 PHP
ThinkPHP、ZF2、Yaf、Laravel框架路由大比拼
2015/03/25 PHP
php按单词截取字符串的方法
2015/04/07 PHP
Symfony2在Nginx下的配置方法图文教程
2016/02/04 PHP
php计算多个集合的笛卡尔积实例详解
2017/02/16 PHP
PHP基于imagick扩展实现合成图片的两种方法【附imagick扩展下载】
2017/11/14 PHP
PHP通过get方法获得form表单数据方法总结
2018/09/12 PHP
javascript实现复选框超过限制即弹出警告框的方法
2015/02/25 Javascript
深入理解JavaScript系列(33):设计模式之策略模式详解
2015/03/03 Javascript
纯JS实现本地图片预览的方法
2015/07/31 Javascript
浅析jQuery中使用$所引发的问题
2016/05/29 Javascript
Vue如何实现组件的源码解析
2017/06/08 Javascript
layer实现关闭弹出层刷新父界面功能详解
2017/11/15 Javascript
ng-events类似ionic中Events的angular全局事件
2018/09/05 Javascript
ligerUI的ligerDialog关闭刷新的方法
2019/09/27 Javascript
javascript设计模式 ? 状态模式原理与用法实例分析
2020/04/22 Javascript
[06:25]第二届DOTA2亚洲邀请赛主赛事第二天比赛集锦.mp4
2017/04/03 DOTA
[36:09]Secret vs VG 2019国际邀请赛淘汰赛 败者组 BO3 第一场 8.24
2019/09/10 DOTA
python实现dnspod自动更新dns解析的方法
2014/02/14 Python
Pyhton中防止SQL注入的方法
2015/02/05 Python
Tensorflow:转置函数 transpose的使用详解
2020/02/11 Python
Python接口测试文件上传实例解析
2020/05/22 Python
django restframework serializer 增加自定义字段操作
2020/07/15 Python
Python利用socket模块开发简单的端口扫描工具的实现
2021/01/27 Python
django使用多个数据库的方法实例
2021/03/04 Python
美国家居装饰店:Z Gallerie
2020/12/28 全球购物
工厂总经理岗位职责
2014/02/07 职场文书
金陵十三钗观后感
2015/06/04 职场文书
2019毕业论文致谢词
2019/06/24 职场文书
2019年“红色之旅”心得体会1000字(3篇)
2019/09/27 职场文书
浅谈Redis的几个过期策略
2021/05/27 Redis
OpenCV绘制圆端矩形的示例代码
2021/08/30 Python
用Python爬取英雄联盟的皮肤详细示例
2021/12/06 Python