责任链模式可以用来消除请求的发送者和接收者之间的耦合。这是通过实现一个由隐式地对请求进行处理的对象组成的链而做到的。链中的每个对象可以处理请求,也可以将其传给下一个对象。
责任链的结构:
责任链由多个不同类型的对象组成,发送者是发出请求的对象,而接收者则是链中那些接收这种请求并且对其进行处理或者传递的对象。请求本身有时也是一个对象,它封装着操作有关的所有数据。其典型的运转流程大致如下:
发送者知道链中的第一个接收者。它向这个接收者发出请求。
每一个接收者都对请求进行分析,然后要么处理它,要么将其往下传。
每一个接收者知道的其它对象只有一个,即它在链中的下家。
如果没有任何接收者处理请求,那么请求将从链上离开。不同的实现对此有不同的反应,既可以无声无息,也可以抛出一个错误。
下面来回顾一下前面的图书馆示例:
var Interface = function () {}; var Publication = new Interface('Publication',['getIsbn','setIsbn','getTitle','setTitle','getAuthor','setAuthor','getGenres','setGenres','display']);
var Library = new Interface('Library',['addBook','findBook','checkoutBook','returnBook']);
var Catalog = new Interface('Catalog',['handleFilingRequest','findBooks','setSuccessor']);
与以前相比,Publication接口只是多了俩个新方法:getGenres 和 setGenres,Library接口新增了一个为图书馆添加藏书的方法,Catalog接口是新增的,他将用来创建保存图书对象的类。
Catalog 接口有三个方法:handleFilingRequest会根据传给它的图书是否符合特定标准而决定是否将其编入内部目录中;findBooks会根据某些参数对内部目录进行搜索;setSuccessor则会设定责任链的下一环。
现在来看一下Book和PublicLibrary这俩个将被重用的类。
var Book = function (isbn,title,author,genres) {
//....
}
Book类现在多了一个用来说明图书所属类别的参数。
PublicLibrary.prototype = {
addBook: function (newBook) {
this.catalog[newBook.getIsbn()] = {book: newBook, available: true};
},
findbooks: function () {
var result = [];
for (var isbn in this.catalog) {
if (!this.catalog.hasOwnProperty(isbn)) {
continue;
}
if (this.catalog[isbn].getTitle().match(searchString) || this.catalog[isbn].getAuthor().match(searchString)) {
result.push(this.catalog[isbn]);
}
}
return result;
},
checkoutBook: function (book) {
var isbn = book.getIsbn();
if (this.catalog[isbn]) {
if (this.catalog[isbn].available) {
this.catalog[isbn].available = false;
return this.catalog[isbn];
} else {
throw new Error('PublicLibrary:book' + book.getTitle() + 'is not currently available.');
}
}else {
throw new Error('PublicLibrary:book' + book.getTitle() + 'is not found.');
}
},
returnBook: function (book) {
var isbn = book.getIsbn();
if (this.catalog[isbn]) {
this.catalog[isbn].available = true;
}else {
throw new Error('PublicLibrary:book' + book.getTitle() + 'is not found.');
}
}
};
现在来实现目录对象,在为这些对象编写代码之前,所有用于判断一本书是否应该编入某个特定目录的代码都封装在Catalog类中。这意味着需要在 PublicLibrary 对象中把每一本书都提供给每一种分类目录进行处理。
function PublicLibrary(books){
this.catalog = {};
this.biographyCatalog = new BiographyCatalog();
this.fantasyCatalog = new FantasyCatalog();
this.mysteryCatalog = new MysteryCatalog();
this.nonFictionCatalog = new NonFictionCatalog();
this.sciFiCatalog = new SciFiCatalog(); for (var i = 0; i < books.length; i++) {
this.addBook(books[i]);
}
}
PublicLibrary.prototype = {
findBooks: function (searchString){},
checkoutBook:function(book){},
returnBook:function(book){},
addBook: function (newBook) {
this.catalog[newBook.getIsbn()] = {book: newBook, available: true};
this.biographyCatalog.handleFillingRequest(newBook);
this.fantasyCatalog.handleFillingRequest(newBook);
this.mysteryCatalog.handleFillingRequest(newBook);
this.nonFictionCatalog.handleFillingRequest(newBook);
this.sciFiCatalog.handleFillingRequest(newBook);
}
}
其中固化了对5个不同类的依赖。如果想增加更多的图书类别,那就需要修改构造函数和addBook方法这俩处代码。此外,这些目录类固化在构造函数中也没有什么意义,因为PublicLibrary的不同实例可能希望拥有完全不同的一套分类目录。而你不可能在对象实例化之后再修改其支持的类别。这些都充分说明了前面的方法并不可取。下面来看责任链模式能带来什么改进。
var PublicLibrary = function (books,firstGenreCatalog) {
this.catalog = {};
this.firstGenreCatalog = firstGenreCatalog; for (var i = 0; i < books.length; i++) {
this.addBook(books[i]);
}
}
PublicLibrary.prototype = {
findBooks: function (searchString){},
checkoutBook:function(book){},
returnBook:function(book){},
addBook: function (newBook) {
this.catalog[newBook.getIsbn()] = {book: newBook, available: true};
this.firstGenreCatalog.handleFillingRequest(newBook);
}
}
这个改进很明显,现在需要保存的只是指向分类目录链中的第一个环节。如果想把一本新书编入各种分类目录中,只需将其传给链中的第一个目录对象即可,每个目录都会把请求往下传。
现在不再有固化在代码中的依赖。所有分类目录都在外部实例化,因此不同的PublicLibrary实例能够使用不同的分类。下面显示了其用法:
var biographyCatalog = new BiographyCatalog();
var fantasyCatalog = new FantasyCatalog();
var mysteryCatalog = new MysteryCatalog();
var nonFictionCatalog = new NonFictionCatalog();
var sciFiCatalog = new SciFiCatalog(); biographyCatalog.setSuccessor(fantasyCatalog);
fantasyCatalog.setSuccessor(mysteryCatalog);
mysteryCatalog.setSuccessor(nonFictionCatalog);
nonFictionCatalog.setSuccessor(sciFiCatalog); var myLibrary = new PublicLibrary(books, biographyCatalog);
// you can add links to the chain whenever you like.
var historyCatalog = new HistoryCatalog();
sciFiCatalog.setSuccessor(historyCatalog);
这个例子中,原来的链上有5个环节,第六个是后来加的。这意味着图书馆每增加一本书都会通过调用链上第一个环节的handleFilingRequest方法发起对该书的编目请求。该请求将沿着目录逐一经过6个目录,最后从链尾离开。链上新增的任何目录都会被挂到链尾。
前面已经考察了使用责任链模式的动机以及与其使用相关的一般结构,但是还没有研究过链上的对象本身。这些链有一个共同的特性,他们都拥有一个指向链上下一个对象successor的引用。链上最后一个对象,这是一个空引用。链上的对象至少都要实现一个共同的方法,即负责处理请求的方法,这些对象不用像前面的例子中那样属于同一个类,但是他们必须实现同样的接口。通常它们分别属于一个类的各种子类,分类目录对象就是这样实现的:
var GenreCatalog = function () {
this.successor = null;
this.catalog = [];
}
GenreCatalog.prototype = {
_bookMatchesCriteria:function (book) {
return false;
},
handleFilingRequest: function (book) {
if (this._bookMatchesCriteria(book)) {
this.catalog.push(book);
}
if (this.successor) {
this.successor.handleFilingRequest(book);
}
},
findBooks: function (request) {
if (this.successor) {
return this.successor.findbook(request);
}
},
setSuccessor: function (successor) {
if (Interface.ensureImplements(successor,Catalog)) {
this.successor = successor;
}
}
}
这个超类提供了所有必须方法的默认实现。他们可以被各种子类继承。子类只需要重写findBooks和_bookMatchesCriteria这俩个方法。其中后一个方法是一个伪私用方法,它负责判断一本书是否应该被编入相关分类目录。GenreCatalog类提供了这俩个方法最简单的实现,以防子类没有重写它们。
从这个超类派生一个分类目录子类很简单:
var SciFiCatalog = function () {};
extend(SciFiCatalog, GenreCatalog);
SciFiCatalog.prototype._bookMatchesCriteria = function (book) {
var genres = book.getGenres();
if (book.getTitle().match(/space/i)) {
return true;
}
for (var i = 0; i < Publication.length; i++) {
var obj = Publication[i];
var genre = genres[i].toLowerCase();
if (genre === 'sci-fi'||genre==='scifi'||genre==='science fiction') {
return true;
}
}
return false;
}
这段代码首先创建了一个空函数,让其继承GenreCatalog,然后实现了_bookMatchesCriteria方法。它这个方法对图书的书名和类别进行检查,判断是否二者中都有一个能够匹配某些搜索用词。
传递请求:
在链上传递请求有许多不同的方法可供选择。最常见的做法就是要么使用一个专门的请求对象,要么就是根本不使用参数,只依靠方法自身传递信息。不用参数调用方法是最简单的办法。在前面的例子中,我们使用了另一种常见技术,即把图书对象作为请求进行传递。图书对象封装了在判断链上那些环节应该将其编入他们的目录时需要的所有数据。这属于将现有对象作为请求对象进行重用的情况。在本节中,我们将实现分类目录的findBooks方法。并将考察如何使用专门的请求对象来在链上的各个环节之间传递数据。
首先,我们需要修改一下PublicLibrary的findBooks方法。以便可以根据类别来缩小搜索范围。如果调用该方法时提供了可选的genres参数,那么搜索将只属于其指定类别的图书中进行:
function PublicLibrary(books){
//...
}
PublicLibrary.prototype = {
addBook: function (newBook) {
//...
},
findbooks: function (searchString,genres) {
if(typeof genres =='object' && genres.length>0) {
var requestObject = {
searchString:searchString,
genres:genres,
results:[]
};
var responseObject = this.firstGenreCatalog.findBooks(requestObject);
return requestObject.results
}else{
var result = [];
for (var isbn in this.catalog) {
if (!this.catalog.hasOwnProperty(isbn)) {
continue;
}
if (this.catalog[isbn].getTitle().match(searchString) || this.catalog[isbn].getAuthor().match(searchString)) {
result.push(this.catalog[isbn]);
}
}
return result;
} },
checkoutBook: function (book) {
//...
},
returnBook: function (book) {
//...
}
};
findBooks方法创建了一个用来封装与请求相关的所有信息的对象。这些信息包含将要搜索的一组类别,搜索用词和一个用来保存查找结果的空数组。
现在我要实现GenreCatalog这个超类中的findBooks方法。这个方法将被用在所有子类中,它不需要重写。下面详细说明。
var GenreCatalog = function () {
this.successor = null;
this.catalog = [];
this.genreName = [];
}
GenreCatalog.prototype = {
_bookMatchesCriteria:function (book) {
//...
},
handleFilingRequest: function (book) {
//...
},
findBooks: function (request) {
var found = false;
for (var i = 0; i < request.genres.length; i++) {
for (var j = 0; j < this.genreName.length; j++) {
if (this.genreName[j]===request.genres[i]) {
found = true;
break;
} }
} if(found){
outerloop:for (var i = 0; i < this.catalog.length; i++) {
var book = this.catalog[i];
if (book.getTitle().match(request.searchString)||book.getAuthor().match(request.searchString)) {
for (var j = 0; j < request.results.length; j++) {
if(request.results[j].getIsbn()===book.getIsbn()) {
continue outerloop;
}
}
request.results.push(book);
} }
} if (this.successor) {
return this.setSuccessor.findBooks(request);
}else{
return request;
}
},
setSuccessor: function (successor) {
//...
}
}
这个方法可以分为三大部分。第一部分逐一检查请求对象中的每一个类别名称,看其是否与对象中保存的一组类别名称中的某一个匹配。如果匹配,那么代码的第二部分会逐一检查目录中的所有图书,看看其书名和作者名是否与搜索词相匹配,匹配的图书将被添加到请求对象results数组中,前提是该数组中没有这本书。最后一部分,如果当前目录对象不是链上的最后一环,那么请求将沿着目录继续下传,否则将返回请求对象。最终请求对象将从链尾开始沿着目录链逐环向上返回,直到返回给客户代码。
在超类GenreCatalog中,用于保存类别名称属性的genreName是一个空数组,在子类中必须为其填入一些具体的类别名称,下面是SciFiCatalog类的实现代码。
var SciFiCatalog = function () {
this.genreName = ['sci-fi', 'scifi', 'fiction'];
};
extend(SciFiCatalog, GenreCatalog);
SciFiCatalog.prototype._bookMatchesCriteria = function (book) {
//...
};
通过把请求封装为一个对象,可以使其更容易管理。在GenreCatalog类的findBooks方法这种复杂的代码中尤其如此。它有助于让搜索用词、类别和搜索结果在通过链上所有必须经过的环节烦人过程中保持完好无缺。
责任链模式的适用场合
适用责任链模式的场合有几种,在图书馆的例子中,我们想发出对某本图书进行分类的请求。我们事先不知道如果可以的话应该被分类到哪一个目录,也不知道可用目录的数目和类型,为了解决这些问题,我们使用了一个目录链,其中每一个目录都会把图像沿链传递给下家。
如果事先不知道在几个对象中有哪些能够处理请求,那么也就应该使用责任链模式。如果这批处理器对象在开发期间不可知,而是需要动态指定的话,那么也应该使用这种模式。该模式还可以用在对于每个请求不不止有一个对象可以对它进行处理这种情况,在图书馆例子中,每本图书都可以被分类到不止一个目录,该请求可以先被一个对象处理,然后进行往下传,在链上可能后面还有另一个对象会处理它。
使用这种模式,可以把特定的具体类与客户隔离开,并代之以一条弱耦合的对象组成的链,他将隐式的对请求进行处理。这有助于提高代码的模块化程度和可维护性。
前面的例子是一个用来显示如何用责任链优化组合对象简单的例子。我们将通过为图片添加标签来进一步阐明这个概念。
标签是一个描述性的标题,可以用来对图片分类。图片和图片库都可以添加标签。为图片库添加标签相当于让所有图片都使用这个标签。你可以在层次体系的任何层次上搜索具有指定标签的图像。这正是责任链的优化资源可用的地方,如果在搜索过程中遇到一个具有所有请求的标签的组合对象节点,那就可以停止请求并将该节点的所有叶节点作为搜索结果返回。
var Composite = new Interface('Composite',['add','remove','getChild','getAllLeaves']);
var GalleryItem = new Interface('', ['hide', 'show', 'addTag', 'getPhotoWithTag']);
我们在接口中添加了三个方法。addTag将为所有调用它的对象以及子对象添加一个标签。getPhotoWithTag返回一个具有特定标签的所有图片组成的数组,而对叶节点使用这个方法则返回一个由它自身组成的数组。
var DynamicGallery = function (id) {
this.children = [];
this.tags = [];
this.element = document.createElement('div');
this.element.id = id;
this.element.className = 'dynamic-gallery';
};
DynamicGallery.prototype = {
addTag: function (tag) {
this.tags.push(tag);
for (var node, i = 0; node=this.getChild(i); i++) {
node.addTag(tag);
}
}
} var GalleryImage = function (src) {
this.element = document.createElement('img');
this.element.className = 'gallery-image';
this.element.src = src;
this.tags = [];
}; GalleryImage.prototype = {
addTag: function (tag) {
this.tags.push(tag);
}
};
我们在组合对象类和叶类中都添加了一个名为tags的数组,它保存着代表标签的字符串。在叶类的addTag方法中,只需要把作为参数的传入的字符串加入tags数组即可。
而在组合类的这个方法中,除了这样做以外,还要把请求在层次体系中往下传递。尽管把标签给予一个组合对象相当于把该标签给予其所有的子对象,但是我们还是的为每一个子对象添加该标签。这是因为搜索可能从层次体系中的任何层次开始,如果没有为每一个叶节点添加标签的话,那么从较低层次开始搜索可能会错过在层次体系中较高层次上分配的标签。
getPhotoWithTag 方法是责任链发挥优化作用的地方。我们将分别讲述每个类中的这个方法,先看组合对象类:
var DynamicGallery = function (id) {
this.children = [];
this.element = document.createElement('div');
this.element.id = id;
this.element.className = 'dynamic-gallery';
};
DynamicGallery.prototype = {
addTag: function (tag) {
this.tags.push(tag);
for (var node, i = 0; node=this.getChild(i); i++) {
node.addTag(tag);
}
},
getAllLeaves: function () {
var leaves = [];
for (var node, i = 0; node < this.getChild(i); i++) {
leaves = leaves.concat(node.getAllLeaves());
}
return leaves;
},
getPhotosWithTag: function (tag) {
for (var i = 0; i < this.tags.length; i++) {
if (this.tags[i]===tag) {
return this.getAllLeaves();
}
}
for (var results = [],node,i=0;node=this.getChild(i);i++) {
results = results.concat(node.getPhotosWithTag(tag));
}
return results;
}
}
这段代码实际上为DynamicGallery添加了俩个方法,getPhotosWithTag 方法是按责任链的风格实现的。它首先要判断当前对象是否能处理请求。其具体做法就是在当前对象的tags数组中检查指定的标签,如果能找到,那就表明层次体系中当前这个组合对象的所有子对象也都具有这个标签,此时即可停止搜索,然后在这个层次上处理请求。如果找不到指定标签,则将请求传递给每一个子对象,并返回结果。
getAllLeaves 方法用于获取特定组合对象的所有叶节点后代节点并将它们组织为一个数组返回,它作为一个普通的数组对象方法实现的,也就是说同样的方法调用会被传递到每一个子对象。
这些方法在叶类中的实现相对简单,它们返回的结果之中都只包含叶对象本身(但getPhotosWithTag方法在当前叶对象不匹配指定标签时返回一个空数组):
var GalleryImage = function (src) {
this.element = document.createElement('img');
this.element.className = 'gallery-image';
this.element.src = src;
this.tags = [];
}; GalleryImage.prototype = {
addTag: function (tag) {
this.tags.push(tag);
},
getAllLeaves: function () {
return [this];
},
getPhotosWithTag: function (tag) {
for (var i = 0; i < this.tags.length; i++) {
if (this.tags[i]===tag) {
return [this];
}
}
return [];
}
};
我们来分析一下本例中使用责任链有什么收获以及组合模式是如何为此提供帮助的。如果把getPhotosWithTag作为一个组合对象方法实现,让它只负责把方法调用传递给每个子对象,那么每个子对象又得把自己的所有标签逐一与所搜索的标签进行比较,我们的做法使用了责任链模式来确定是否可以早一点结束 搜索。在最差的情况下,最后仍然是叶节点在处理请求,但要是某个组合对象节点具有那个标签的话,那么请求可能在层次体系中往上几个层次的地方就能得到处理。
实际上,组合对象方法需要耗费的计算量越大,在层次体中较高的层次上处理请求并使用某种辅助方法(比如getAllLeaves)提高请求的处理效率所带来的好处也就越大。
责任链模式的利与弊:
借助责任链模式,可以动态选择由那个对象处理请求。这意味着你可以使用只有在运行期间才能知道的条件来把任务分派给最恰当的对象。你还可以使用这个模式消除发出请求的对象与处理请求的对象之间的耦合。藉此你可以在模块化的组织方法获得更大的灵活性,而且在重构和修改代码的时候也不用担心会把类名固化在算法中。
在已经有现成的链或者层次体系的情况下,责任链模式更加有效。与组合模式的结合使用就属于这种情况。你可以重用组合对象的结构来传递请求,直到找到一个可以处理请求的对象。在此情况下,不用编写粘合性代码来实例化那些对象或建立链,因为那些东西早已准备妥当。藉此可以实现把请求转给恰当的处理程序的方法。
弊端:
在责任链模式中,请求与具体处理程序被隔离开来。因此无法保证它一定会被处理,而不是直接从链尾离开。也无法得知由那个请求具体处理它。
责任链和组合对象的搭配使人困惑,人们原本期望认为,组合对象节点完全可以与叶节点互换使用,而且客户代码也看不出其中的差别,所有方法调用都被组合对象往层次体系的下层传递,但是责任链方法改变了这个约定。引入责任链之后,有些方法会在组合对象这里进行处理,而不会继续往下传。想要让这些方法可以与叶方法互换,其代码的编写会很棘手,他们的效率很高,但为此付出的代价是代码的复杂性。
读书笔记之 - javascript 设计模式 - 责任链模式的更多相关文章
-
再起航,我的学习笔记之JavaScript设计模式27(链模式)
链模式 概念介绍 链模式(Operatc of Responsibility): 通过在对象方法中将当前对象返回,实现对同一个对象多个方法的链式调用.从而简化对该对象的多个方法的多次调用时,对该对象的 ...
-
读书笔记之 - javascript 设计模式 - 享元模式
本章探讨另一种优化模式-享元模式,它最适合于解决因创建大量类似对象而累及性能的问题.这种模式在javascript中尤其有用,因为复杂的javascript代码很快就会用光浏览器的所有可用内存,通过把 ...
-
读书笔记之 - javascript 设计模式 - 装饰者模式
本章讨论的是一种为对象增添特性的技术,它并不使用创建新子类这种手段. 装饰者模式可以透明地把对象包装在具有同样接口的另一对象之中,这样一来,你可以给一些方法添加一些行为,然后将方法调用传递给原始对象. ...
-
java 设计模式 -- 责任链模式
设计模式 – 责任链模式 介绍: 责任链模式是一种动态行为模式,有多个对象,每一个对象分别拥有其下家的引用.连起来形成一条链.待处理对象则传到此链上,在此链进行传递,且待处理对象并不知道此会被链上的哪 ...
-
浅谈Python设计模式 -- 责任链模式
声明:本系列文章主要参考<精通Python设计模式>一书,并且参考一些资料,结合自己的一些看法来总结而来. 之前在最开始就聊了Python设计模式有三种,其中关于创建型和结构型设计模式基本 ...
-
[设计模式] javascript 之 责任链模式
责任链模式:定义 责任链接模式又称职责链模式,是一种对象的行为模式:它是一种链式结构,每个节点都有可能两种操作,要么处理该请求停止该请求操作,要么把请求转发到下一个节点,让下一个节点来处理请求:该模式 ...
-
我的Java设计模式-责任链模式
今天来说说程序员小猿和产品就关于需求发生的故事.前不久,小猿收到了产品的需求. 产品经理:小猿,为了迎合大众屌丝用户的口味,我们要放一张图,要露点的. 小猿:......露点?你大爷的,让身为正义与纯 ...
-
Java设计模式-责任链模式
提出问题: 最初接触责任链模式就是在struts2中,在当时学的时候看了一眼,大概知道了原理,最近在复习,模拟struts2,说是模拟只是大体模拟了struts2的工作流程,很多东西都是写死的,只是为 ...
-
Java与设计模式-责任链模式
责任链模式属于行为型设计模式之中的一个,怎么理解责任链?责任链是能够理解成数个对象首尾连接而成,每个节点就是一个对象.每个对象相应不同的处理逻辑,直至有一个对象响应处理请求结束.这一种模式成为责任链模 ...
随机推荐
-
iOS基础-NSString及NSMutableString剖析
一.NSString头文件 NSString : NSObject 实现协议: NSCopying/NSMutableCopying/NSSecureCoding 类别: //扩展类别 NSStrin ...
-
hibernate------java-delete-insert-update
**************************************************************************************************** ...
-
ADT公司G729 方案指标
ADT公司G729 方案指标 G.729 Voice Compression Algorithm and its many annexes G.729 is used in wireless voic ...
-
long long 与 _int64
本文讨论的是五种常用的C/C++编译器对64位整型的支持,这五种编译器分别是gcc(mingw32),g++(mingw32),gcc(linux i386),g++(linux i386),Micr ...
-
(easy)LeetCode 217.Contains Duplicate
Given an array of integers, find if the array contains any duplicates. Your function should return t ...
-
Spark的部署方式
1.Spark的应用程序部署 2.Spark的集群部署
-
读书笔记系列01-《收获、不止Oracle》
读书笔记系列01-<收获.不止Oracle> 最近计划将看过的Oracle书籍依次系统的总结下读书笔记. 这本书是我个人觉得写的最有趣的Oracle书籍,也是我接触Oracle后第一本完全 ...
- javascript引擎任务运行顺序
-
js生成指定范围的随机数
<!doctype html> <html lang="en"> <head> <meta http-equiv="Conten ...
-
使用requests模块post payload请求
import json import requests import datetime postUrl = 'https://sellercentral.amazon.com/fba/profitab ...