[Effective JavaScript 笔记]第57条:使用结构类型设计灵活的接口
想象创建wiki的库。wiki网站包含用户可以交互式地创建、删除和修改的内容。许多wiki都以简单、基于文本标记语言创建内容为特色。通常,这些标记语言只提供了HTML可用功能的一个子集,但是却有一个更简单、更清晰的源格式。例如,环绕星号的文本被格式化为粗体,环绕下划线的被格式化为带有下划线的文本,环绕斜杠的被格式化为斜体。用记可以输入如下格式:
this sentence contains a *bold phrase* within it.
this sentence contains a _underlined phrase_ within it.
this sentence contains a /italicized phrase/ within it.
该网站会以下面的形式将内容呈现给wiki读者
this sentence contains a bold phrase within it.
this sentence contains a underlined phrase within it.
this sentence contains a italicized phrase within it.
一个灵活的wiki库应该给应用程序编写者提供可供选择的标记语言。
要给应用程序编写者提供备选的标记语言,需要将提取用户创建的标记源文本内容的功能与其他功能分离。其他的wiki功能包括账记管理、修订历史记录和内容存储等。其余的应用程序可以通过一套文档完善的属性和方法的接口与提取出的功能交互。通过针对接口完备API的严格编程,并忽略这些方法的实现细节,从而不论选择使用哪个源格式,应用程序都能正常运行。
更加深入地了解需要什么样的wiki内容提取接口。wiki库必须能提取元数据,如页面标题和作者信息等,并能将页面内容格式化为HTML呈现给wiki读者。可以将wiki中的每一个页面表示为提供了通过getTitle、getAuthor和toHTML页面方法获取这些数据的一个对象。
接下来,该wiki库需要提供创建一个自定义wiki格式化器的应用程序的方法,以及一些针对流行标记格式的内置格式化器。例如,应用程序的编写者可能希望使用MediaWiki格式化器(这是维基百科所使用的格式)。
var app=new Wiki(Wiki.formats.MEDIAWIKI);
该wiki库将该格式化函数存储在wiki实例对象的内部
function Wiki(format){
this.format=format;
}
每当读者想要查看页面时,应用程序都会检索出其源文本并使用内部的格式化器将源文本渲染为HTML页面。
Wiki.prototype.displayPage=function(source){
var page=this.format(source);
var title=page.getTitle();
var author=page.getAuthor();
var output=page.toHTML();
//...
}
类似Wiki.formats.MEDIAWIKI的格式化器是如何实现的?熟悉基于类编程的程序员可能倾向于创建一个Page的基类,该Page类表示用户创建的内容,每个Page的子类实现不同的格式。MediaWiki格式化可能实现为一个继承Page的MWPage类,而MEDIAWIKI则是返回MWPage实例的“工厂函数”。
function MWPage(source){
Page.call(this,source);
//...
}
MWPage.prototype=Object.create(Page.prototype);
MWPage.prototype.getTitle=function(){};
MWPage.prototype.getAuthor=function(){};
MWPage.prototype.toHTML=function(){};
wiki.formats.MEDIAWIKI=function(source){
return new MWPage(source);
}
这里由于MWPage类需要自己实现所有wiki应用程序需要的getTitle,getAuthor,toHTML方法,Page基类并没有起到什么作用。而且displayPage方法并不需要关心页面对象的继承体系。它只需要实现如何显示页面的相关方法。wiki格式的实现很自由,任何能完成功能的代码都可以。
使用简单对象字面量构建MediaWiki页面格式的接口实现通常已经足够了。
Wiki.formats.MEDIAWIKI=function(source){
//....
return {
getTitle:function(){},
getAuthor:function(){},
toHTML:function(){}
}
};
继承有时会导致比它解决的更多的问题。当几个不同的wiki格式共享不相重叠的功能集时,继承的问题就出现了。没有任何继承结构才是对的。例如:
Format A:*bold*,[link],/italics/
Format B:**bold**,[[link]],*italics*
Format A:**bold**,[link],*italics*
我们想要实现各个部分的功能来识别每种不同类型的输入,然而功能的混合和匹配并没有映射到任何清晰的A,B和C之间的继承层次关系。正确的做法是分别实现每种输入匹配的函数,然后根据每种格式的需要混合和匹配功能。
注意消除Page基类,这里不需要用任何东西来替代它。任何人希望实现一个新的自定义格式都可以,而不需要在某处“注册”它。只要displayPage方法结构正确,具有预期行为的getTitle,getAuthor,toHTML方法,那么它就适用任何js对象。
这种接口有时称为结构类型或鸭子类型。任何对象只要具有预期的结构就属于该类型(看起来像只鸭子,或叫声像只鸭子)。在js中这是一种优雅、轻量的编程模式,因为不需要编写显示的声明。一个调用某个对象方法的函数能够与任何实现了相同接口的对象一起工作。当然你在API的文档中列出对象接口的预期结构。这样接口实现者便会知道哪些属性和方法是必需的以及库和应用程序期望的行为是什么。
灵活的结构类型的另一个好处是有利于单元测试。wiki库可能期望嵌入一个HTML服务器对象来实现wiki网站的网络功能。如果想要在没有连接网络的情况下测试wiki网站的交互时序,那么可以实现一个mock对象来模拟HTTP服务器的行为。这些行为是遵照预定的脚本而不是真实的接触网络。这种方式提供了与虚拟服务器重复交互的行为,而不是依赖不可预知的网络行为。这使用测试组件与服务器的交互行为成为可能。
提示
使用结构类型(也称为鸭子类型)来设计灵活的对象接口
结构接口更灵活、更轻量,所以应该避免使用继承
针对单元测试,使用mock对象即接口的替代实现来提供可复检的行为
[Effective JavaScript 笔记]第57条:使用结构类型设计灵活的接口的更多相关文章
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第22条:使用arguments创建可变参数的函数
第21条讲述使用可变参数的函数average.该函数可处理任意数量的参数并返回这些参数的平均值. 如何创建可变参数的函数 1.实现固定元数的函数 书上的版本 function averageOfArr ...
- [Effective JavaScript 笔记]第58条:区分数组对象和类数组对象
示例 设想有两个不同类的API.第一个是位向量:有序的位集合 var bits=new BitVector(); bits.enable(4); bits.enable([1,3,8,17]); bi ...
- [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑
构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...
- [Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列
第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列.使用下面代码,可以很容易使一个应用程序陷入泥潭. while(true){} 而且它并不需要一个无限循环来写一个缓慢的程序.代码 ...
- [Effective JavaScript 笔记]第63条:当心丢弃错误
管理异步编程的一个是错误处理.同步代码中只要使用try语句块包装一段代码很容易一下子处理所有的错误. try{ f(); g(); h(); } catch(e){ //这里用来下得出现的错误 } t ...
随机推荐
- swift项目实战FoodPin目录
好吧,据说写博客能够找到好工作,那我也来分享一个项目吧! 自己自学iOS开发也有半年多了,现在就来分享一个swift的小项目吧!这个项目的来源是<Beginning iOS8 programmi ...
- javascript基于原型实现面向对象
传统的OO语言有类的概念,但js(ES5)却是基于原型实现的面向对象. 原型是?我们创建的每一个函数都会有一个原型(prototype)属性,这个属性是一个指针,指向函数的原型(prototype)对 ...
- css限制图片大小,避免页面撑爆
/*==========限制图片大小======避免页面撑暴========*/img { max-width:100%;width:expression(width>669?"100 ...
- cryptdb中wrapper.lua的分析
因为cryptDB是在mysql-proxy的基础上来实现了,可以看成是为mysql-proxy添加了新的.为mysql-proxy已经为开发人员提供了相应的接口.如果开发人员只需要通过lua脚本语言 ...
- centos 6.5下安装mysql+nginx+redmine 3.1.0 笔记
centos 6.5下安装mysql+nginx+redmine 3.1.0 笔记 目录[-] 过程 1.安装RVM 2.利用rvm安装 Ruby 1.9.3 并设为默认 3.安装rails 4.安装 ...
- Beta项目冲刺汇总贴
第一天 http://www.cnblogs.com/Allenbi/p/5003704.html 第二天 http://www.cnblogs.com/Allenbi/p/5020820.html ...
- G-nav-04
'use strict';define([ 'jquery'], function($) { var nav = { init : function() { $("#burger-menu& ...
- codevs 1835 魔法猪学院 A*寻k短路做了一个月卡死在spfa那了/(ㄒoㄒ)/~~
SPFA时点出队后一定要把在队内的标记置为false!SPFA时点出队后一定要把在队内的标记置为false!SPFA时点出队后一定要把在队内的标记置为false! 我因为这个卡了一个月大家信吗?测得时 ...
- Java设计模式-责任链模式(Chain of Responsibility)
接下来我们将要谈谈责任链模式,有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求.但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任 ...
- POJ1523 SPF
Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 8254 Accepted: 3772 Description Consi ...