[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 ...
随机推荐
- Windows Phone8 中如何引用 SQLite 数据库2
本博文编写环境 VS2013 + WP8 SDK 上篇介绍完了SQLite在wp中的部署(具体请参阅 Windows Phone8 中如何引用 SQLite 数据库),下面来看如何使用 SQLite ...
- EntityFramework中的线程安全,又是Dictionary
继上次记一次w3wp占用CPU过高的解决过程(Dictionary和线程安全)后又再次与Dictionary博弈,这一次是在EntityFramework中的Dictionary. 从一个异常说起 这 ...
- Coding the Matrix (2):向量空间
1. 线性组合 概念很简单: 当然,这里向量前面的系数都是标量. 2. Span 向量v1,v2,.... ,vn的所有线性组合构成的集合,称为v1,v2,... ,vn的张成(span).向量v1, ...
- WCF入门(9)
前言 上次搬家空调出了点问题,和修空调的师傅商量了一下,感觉还是讲理的. 今天又在公司基本没有任何存在感的过了一天,纠结...领导还不在... 前些天往手机里面放了几集WCF入门视频,今天用暴风影音看 ...
- C# 使用XML序列化对象(一)
在System.Xml.Serialization命名空间中提供了XML序列化类XmlSerializer用于将对象序列化为XML. 下面看一个最简单的例子: public class A { pub ...
- Moqui学习之 Step by Step OrderProcureToPayBasicFlow
/** Get a service caller to call a service synchronously. */ //ServiceCallSync sync(); /** Map of na ...
- java.lang.NoClassDefFoundError: antlr/ANTLRException
在用Hibernate进行查询时,出现这样的错误:Exception in thread "main" java.lang.NoClassDefFoundError: antlr/ ...
- 搭建OpenWrt开发环境(包括编译过程)
OpenWrt是一个高度模块化.高度自动化的嵌入式linux发行版,其编译和安装过程比普通的linux发行版而言,要简单太多了.如果您是新手,您那恐惧的心大可放到肚子里,呵呵.对于新手来说最麻烦的恐怕 ...
- hdu2846 字典树
给你一堆字符串,然后再给你几个查询,前面那些字符串中有多少个包含了这个串.所以可以把开始inset()的字符遍历一遍,同时可能出现该字符串在某个字符串中有多次出现,所以还要用flag标记,来区分不同的 ...
- Yii2 RBAC 用到的表
Yii2 RBAC用到的四张auth表位于 vendor/yiisoft/yii2/rbac/migration文件夹里面,可以用migration生成 yii migrate --migration ...