今天我们就戴上计算机科学的帽子,学习一些常见的设计模式。设计模式为开发人员提供了一种以可重用和优雅的方式解决技术问题的方法。有兴趣成为更好的JavaScript开发人员吗?然后继续读。
每隔几周重新发布一次教程,我们会重温网站历史上一些读者最喜欢的帖子。本教程首次发布于2012年7月。
可靠的设计模式是可维护软件应用程序的基本构建块。如果你曾经参加过技术面试,你会喜欢被问到这些问题。在本教程中,我们将介绍一些你现在就可以开始使用的模式。
什么是设计模式?设计模式是可重用的软件解决方案。
简单地说,设计模式是针对软件开发中经常出现的特定类型问题的可重用软件解决方案。经过多年的软件开发实践,专家们已经找到了解决类似问题的方法。这些解决方案已经封装在设计模式中。所以:
模式是经过验证的软件开发问题。解决方案模式是可扩展的,因为它们通常是结构化的,并且有您应该遵循的规则。模式可以被重用来解决类似的问题。在本教程中,我们将进一步介绍一些设计模式的例子。
设计模式的类型在软件开发中,设计模式通常分为几类。我们将在本教程中介绍三个最重要的内容。下面简单解释一下:
创建模式关注于如何创建对象或类。这听起来可能很简单(在某些情况下确实如此),但是大型应用程序需要控制对象创建过程。& ltp & gt& lt/p & gt;结构设计模式侧重于管理对象之间关系的方法,以便以可扩展的方式构建应用程序。结构模式的一个关键方面是确保应用程序一部分的变化不会影响所有其他部分。& ltp & gt& lt/p & gt;行为模式关注对象之间的交流。看完这些简短的说明后,您可能仍有疑问。这是自然的。一旦我们深入研究一些设计模式,事情就会变得清晰。所以请继续读下去!
JavaScript中关于类的注释在阅读设计模式时,经常会看到对类和对象的引用。这可能会令人困惑,因为JavaScript并没有真正的“类”结构;更正确的说法是“数据类型”。
JavaScript中的数据类型JavaScript是一种面向对象的语言,其中对象以原型继承的概念继承其他对象。您可以通过定义所谓的构造函数来创建数据类型,如下所示:
函数Person(config){ this . name = config . name;this . age = config . age;} person . prototype . get age = function(){ return this . age;};var tilo =新联系人({ name:& quot;蒂洛& quot,年龄:23 });console . log(tilo . getage());请注意,在Person数据类型上定义方法时使用了prototype。由于多个Person对象将引用同一个原型,这允许getAge()方法由Person数据类型的所有实例共享,而不是为每个实例重新定义它。此外,从Person继承的任何数据类型都可以访问getAge()方法。
JavaScript中处理隐私的另一个常见问题是没有真正的私有变量。然而,我们可以使用闭包来模拟隐私。考虑下面的代码片段:
var retinaMacbook =(function(){//私有变量var RAM,addRAMRAM = 4;//私有方法addRAM = function(additional RAM){ RAM+= additional RAM;};return {//公共变量和方法USB: undefined,insert USB:function(device){ this。USB =设备;},remove USB:function(){ var device = this。USB这个。USB =未定义;返回装置;} };})();在上面的例子中,我们用公共和私有变量和方法创建了一个retinaMacbook对象。我们是这样使用它的:
retina macbook . insert USB(& quot;myUSB & quot);console.log(retinaMacbook。USB);//注销& quotmyUSB & quotconsole . log(retina macbook . ram)//注销未定义我们可以用JavaScript中的函数和闭包做更多的事情,但我们不会在本教程中详细介绍所有内容。有了这个关于JavaScript数据类型和隐私的小教训,我们可以继续学习设计模式。
有许多不同类型的创造性设计模式,但是我们将在本教程中介绍其中的两种:构建器和原型。我发现这些内容的使用频率足够吸引人的注意力。
生成器模式生成器模式通常用于Web开发。你可能以前用过,只是没有意识到。简单地说,这种模式可以定义如下:
例如,您可能已经在jQuery中执行了无数次:
var my div = $(‘ & lt;div id = & quotmyDiv & quot& gt这是一个div。& lt/div & gt;’);//myDiv现在表示引用DOM节点的jQuery对象。p/>;’);//someText是引用HTMLParagraphElementvar input = $(‘ & lt;输入/>;’);看一下上面的三个例子。第一个,我们进去了
$变量采用jQuery中的Builder模式。在每个示例中,我们都返回了一个jQuery DOM对象,并可以访问jQuery库提供的所有方法,但我们从未显式调用document.createElement库在幕后处理所有这些事情。
想象一下,如果我们必须显式地创建DOM元素并将内容插入其中,那将需要做多少工作!通过使用构建器模式,我们可以专注于对象的类型和内容,而不是显式地创建它。
在原型模式之前,我们介绍了如何通过函数在JavaScript中定义数据类型,以及如何向对象的原型添加方法。原型模式允许对象通过原型从其他对象继承。
这是在JavaScript中实现继承的一种简单而自然的方式。例如:
var Person = { numFeet: 2,numHeads: 1,numHands:2 };//Object.create取其第一个参数,应用于你的新object . var tilo = object . create(Person)的原型;console . log(tilo . numheads);//outputs 1 tilo . numheads = 2;console . log(tilo . numheads)//outputs 2 person对象中的属性(和方法)应用于tilo对象的原型。如果我们希望它们不同,我们可以重新定义tilo对象的属性。
在上面的例子中,我们使用了Object.create()。但是,Internet Explorer 8不支持更新的方法。在这些情况下,我们可以模拟它的行为:
var vehicle prototype = { init:function(carModel){ this . model = carModel;},get model:function(){ console . log(& quot;这辆车的型号是& quot+this . model);}};函数vehicle(model){ function F(){ };f .原型=车辆原型;var F = new F();f.init(模型);返回f;}var汽车=车辆(& quot福特Escort & quot);car . get model();这种方法的唯一缺点是不能指定只读属性,但可以在使用Object.create()时指定。然而,原型模式显示了对象如何从其他对象继承。
当弄清楚系统应该如何工作时,结构设计模式非常有用。它们使我们的应用程序易于扩展和维护。本组我们将研究以下模式:复合模式和外观模式。
复合模式(Composite pattern)复合模式(Composite pattern)是另一种你以前可能用过但没有意识到的模式。
那么这意味着什么呢?好吧,考虑jQuery中的这个例子(大多数JS库都有等价的例子):
$(‘.my list’)。add class(“selected”);$(‘#myItem ‘)。add class(“selected”);//不要在大表上这样做,这只是一个例子。$(& quot;# dataTable tbody tr & quot).打开(& quot单击& quot,函数(事件){ alert($(这个)。text());});$(‘#myButton ‘)。打开(& quot单击& quot,函数(事件){ alert(& quot;点击了。”);});无论我们是处理单个DOM元素还是一组DOM元素,大多数JavaScript库都提供了一致的API。在第一个示例中,我们可以将选定的类添加到。myList选择器,但是我们可以在处理单个DOM元素#myItem时使用相同的方法。类似地,我们可以使用on()方法在多个节点上附加事件处理程序,或者通过相同的API在单个节点上附加事件处理程序。
通过使用复合模式,jQuery(和许多其他库)为我们提供了一个简化的API。
复合模式有时会导致问题。在像JavaScript这样的松散类型语言中,知道我们处理的是单个元素还是多个元素通常会很有帮助。因为复合模式对两者使用相同的API,所以我们经常会把一个错当成另一个,最终会犯意想不到的错误。有些库(比如YUI3)提供了两个单独的方法来获取元素(Y.one()和Y.all())。
外观模式这是另一种我们习以为常的常见模式。事实上,这是我的最爱之一,因为它很简单,我见过它在任何地方被用来帮助解决浏览器不一致的问题。以下是外观模式的含义:
外观模式几乎总能提高软件的可用性。再以jQuery为例。这个库中最流行的方法之一是ready()方法:
$(文档)。ready(function(){//所有代码都在这里…});ready()方法实际上实现了一个facade。如果您查看源代码,您会发现以下内容:
就绪:(function() {…//Mozilla、Opera和Webkit if(document . addevent listener){ document . addevent listener(& quot;DOMContentLoaded & quot,幂等_fn,false);…} //IE事件模型else if(document . attach event){//确保在onload之前触发;对于iframes document . attach event(& quot;onreadystatechange & quot,幂等_ fn);//回退到window.onload,它将始终工作加载& quot,幂等_ fn);在底层,ready()方法没有这么简单。JQuery调节浏览器的不一致性,以确保ready()在适当的时间被触发。但是,作为开发者,你会看到一个简单的界面。
大多数外观模式示例都遵循这一原则。在实现中,我们通常依赖底层的条件语句,但是将它们作为一个简单的接口呈现给用户。实现这种模式的其他方法包括animate()和css()。你能想到这些为什么用外观模式吗?
行为设计模式任何面向对象的软件系统都会在对象之间进行通信。未能组织好这种沟通可能会导致难以发现和修复的错误。行为设计模式指定了组织对象间通信的不同方式。在本节中,我们将研究观察者模式和中介者模式。
观察者模式观察者模式是我们将要经历的两种行为模式中的第一种。事情是这样的:
听起来很简单,对吧?我们需要三种方式来描述这种模式:
Publish(data):当主题有通知要发送时调用。一些数据可以通过这个方法传递。Subscribe(observer):由主题调用,将一个观察者添加到它的观察者列表中。Unsubscribe(observer):由一个主题调用,从它的观察者列表中删除一个观察者。事实证明,大多数现代JavaScript库都支持这三种方法,并将其作为自定义事件基础设施的一部分。通常,有一个on()或attach()方法,一个trigger()或fire()方法,以及一个off()或detach()方法。考虑下面的代码片段:
//我们只是在jQuery events方法//和Observer模式规定的方法之间创建一个关联,但您不必这样做。var o = $({ });$.subscribe = o . on . bind(o);$.unsubscribe = o . off . bind(o);$.publish = o . trigger . bind(o);//usage document . on(‘ tweets received ‘,function(tweets){//执行一些操作,然后触发一个事件$。发布(‘ tweetsShow ‘,推文);});//我们可以订阅这个事件,然后触发我们自己的事件。$.subscribe( ‘tweetsShow ‘,function(){//以某种方式显示推文..//在显示后发布一个操作。$.发布(“tweets displayed”);});$.subscribe(‘tweetsDisplayed,function() {…});观察者模式是实现起来相对简单的模式之一,但是它非常强大。JavaScript非常适合这种模式,因为它本质上是基于事件的。下次开发Web应用程序时,请考虑开发松散耦合的模块,并采用观察者模式作为通信模式。如果涉及太多的主体和观察者,观察者模式可能会有问题。在大规模系统中可能会出现这种情况,我们研究的下一个模型将试图解决这个问题。
中介模式我们要讨论的最后一个模式是中介模式。它类似于观察者模型,但是有一些显著的不同。
现实世界中一个很好的类比是空中交通塔台,它处理机场和航班之间的通信。在软件开发领域,当系统变得过于复杂时,通常使用中介模式。通过放置中介,您可以通过单个对象处理通信,而不是让多个对象相互通信。从这个意义上说,中介模式可以用来代替实现观察者模式的系统。
在这一点上,Addy Osmani提供了中介模式的简化实现。下面说说怎么用吧。假设您有一个Web应用程序,允许用户点击专辑并播放其中的音乐。您可以像这样设置一个中介:
$(‘#album ‘)。on(‘click ‘,函数(e){ e . prevent default();var albumId = $(这个)。id();mediator . publish(& quot;播放相册& quot,albu mid);});var play album = function(id){…mediator . publish(& quot;albumStartedPlaying & quot,{歌曲列表:[..],current song:& quot;没有你& quot});};var logAlbumPlayed = function(id){//在后台记录相册};var Update user interface = function(album){//更新UI以反映正在播放的内容};//Mediator subscription Mediator . subscribe(& quot;播放相册& quot,play album);mediator . subscribe(& quot;播放相册& quot,logAlbumPlayed);mediator . subscribe(& quot;albumStartedPlaying & quot,update user interface);这种模式相对于观察者模式的优势在于,单个对象负责通信,而在观察者模式下,多个对象可以相互监听和订阅。
在观察者模式下,没有封装约束的单个对象。相反,观察者和主体必须合作来维持约束。交流方式由观察者和主体的互联方式决定:一个主体通常有多个观察者,有时一个主体的观察者是另一个观察者的主体。
结论该方法已成功应用于实践。
设计模式的伟大之处在于它在过去被成功地应用过。有很多开源代码可以用JavaScript实现各种模式。作为开发人员,我们需要了解现有的模式以及何时应用它们。我希望这篇教程能帮助你在回答这些问题时走得更远。
本文的大部分补充阅读可以在Addy Osmani的优秀著作《学习JavaScript设计模式》中找到。这是一本在知识共享许可下免费出版的在线书籍。这本书涵盖了许多不同模式的理论和实现,包括常见的JavaScript和各种JS库。我鼓励你在开始下一个项目时,把它作为参考。