[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 ...
随机推荐
- 软工实践练习——使用git进行代码管理心得
一.在Github上注册账户.其中创建organization在小组成员的账户上创建,并在其账户上创建了小组的版本库.在创建organization的过程中,参考了助教提供的博客:http://sef ...
- web服务器
1.打破信息孤岛,实现信息的集成 2.配置文件 web.xml 定义自己的服务器应该要哪些功能! 3.tomcat 是一个servlet容器,一个web服务器. 部署:将web应用 ...
- OC基础--构造方法
OC语言中类的构造方法学了两种: 一.方法一:[类名 new] 例:[Person new] 缺点:可扩展性不强,假如在Person类中有_age 成员变量,在初始化时想让_age 中的值为20,ne ...
- C基础之递归(思想很重要,学会找规律)
递归思想的条件:1.函数自己调用自己 2.函数必须有一个固定的返回值(如果没有这个条件会发生死循环) ----规律很重要 简单递归题目一: 设计一个函数计算一个整数的n次方,比如2的3次方,就是8 步 ...
- Linq 分页不可缺少的两个方法
//LINQ分页的方法 //1.获取总页数 public int GetPageCount(int pageSize)//pageSize是每页的行数 { //先查出总共有多少行 int rowCou ...
- hdu2807 矩阵乘法+floyd
网上有优化的方法 就是乘上一个一维的矩阵:现在还没有想通.想通了不上代码: 我用的就是普通的矩阵,压着时间过:只是多了一个判断条件,不加这个条件就超时: #include<stdio.h> ...
- Statement,PreparedStatement和CallableStatement的联系和区别
联系: CallableStatement继承自PreparedSatement,PreparedStatement继承自Statement. 区别: 1:Statement 每次执行sql语句,数据 ...
- Sqlserver 读取EXCEL
1.1 启用本地读取设置 --启用EXEC sp_configure 'show advanced options', 1RECONFIGUREEXEC sp_configure 'Ad Hoc Di ...
- Web Api如何传递POST请求
这里记录一次Web Api传递post请求的例子,由于使用了默认工程的例子,方法名的参数值标记头为FromBody的形式,如下图所示的调用: 调用方式: 那么如果要两个以上的参数如何去实现,这种方式是 ...
- [IOS SQLITE的使用方式]
1.把数据库文件localdata.db放入工程,并建立bundle(在build phases里) 2.创建新的类,用于本地SQLite查询. LocalDB.m(.h就不说了,保证每个.m里要外部 ...