想象创建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条:使用结构类型设计灵活的接口的更多相关文章

  1. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  2. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  3. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  4. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  5. [Effective JavaScript 笔记]第22条:使用arguments创建可变参数的函数

    第21条讲述使用可变参数的函数average.该函数可处理任意数量的参数并返回这些参数的平均值. 如何创建可变参数的函数 1.实现固定元数的函数 书上的版本 function averageOfArr ...

  6. [Effective JavaScript 笔记]第58条:区分数组对象和类数组对象

    示例 设想有两个不同类的API.第一个是位向量:有序的位集合 var bits=new BitVector(); bits.enable(4); bits.enable([1,3,8,17]); bi ...

  7. [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑

    构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...

  8. [Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列

    第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列.使用下面代码,可以很容易使一个应用程序陷入泥潭. while(true){} 而且它并不需要一个无限循环来写一个缓慢的程序.代码 ...

  9. [Effective JavaScript 笔记]第63条:当心丢弃错误

    管理异步编程的一个是错误处理.同步代码中只要使用try语句块包装一段代码很容易一下子处理所有的错误. try{ f(); g(); h(); } catch(e){ //这里用来下得出现的错误 } t ...

随机推荐

  1. Orchard基本概念

    本文链接:http://www.cnblogs.com/souther/p/4531273.html Orchard是个CMS(这不是废话么),它的首要目标是帮助你从现有的碎片建设网站.这些碎片大小不 ...

  2. 18.C#扩展方法(十章10.1-10.2)

    今天的话题,我们来聊下扩展方法,自己也真心感叹自己的文笔,那叫一个惨啊,回顾写的文章,看着看着也忘记当时是怀着什么心态写的,哈哈,现代人真心是太随性了,可能也是太冷漠了,接着写的吧,总是会有帮助,也会 ...

  3. EntityFramework_MVC4中EF5 新手入门教程之六 ---6.通过 Entity Framework 更新关联数据

    在前面的教程中,您将显示相关的数据 :在本教程中,您会更新相关的数据.对于大多数的关系,这个目标是可以通过更新相应的外键字段来达到的.对于多对多关系,实体框架并不直接,暴露联接表,因此您必须显式添加和 ...

  4. AngularJs-数据绑定

    前言: 我们在做前端工作最重要的是把数据能展示给用户看,展示的时候就是把数据绑定给某个元素. 1,简单的数据绑定 html: <!DOCTYPE html> <html ng-app ...

  5. 每天一个linux命令(29):date命令

    在linux环境中,不管是编程还是其他维护,时间是必不可少的,也经常会用到时间的运算,熟练运用date命令来表示自己想要表示的时间,肯定可以给自己的工作带来诸多方便. 1.命令格式: date [参数 ...

  6. Future模式

    Future模式简介 Future模式有点类似于网上购物,在你购买商品,订单生效之后,你可以去做自己的事情,等待商家通过快递给你送货上门.Future模式就是,当某一程序提交请求,期望得到一个答复.但 ...

  7. html之给文本框设置宽度和高度/input的无边框效果

    <input name="" type="text" style="width:200px; height:20px;" /> ...

  8. 【BZOJ】3527: [Zjoi2014]力(fft+卷积)

    http://www.lydsy.com/JudgeOnline/problem.php?id=3527 好好的一道模板题,我自己被自己坑了好久.. 首先题目看错.......什么玩意.......首 ...

  9. std::ios::sync_with_stdio(false);

    这句语句是用来取消cin的同步,什么叫同步呢?就是iostream的缓冲跟stdio的同步.如果你已经在头文件上用了using namespace std;那么就可以去掉前面的std::了.取消后就c ...

  10. 【CodeForces 312B】BUPT 2015 newbie practice #3A Archer

    题 SmallR is an archer. SmallR is taking a match of archer with Zanoes. They try to shoot in the targ ...