设计原则(SOLIDPrinciples)在编程领域,SOLID(SingleFunction,OpenandClosedPrinciple,LiskovSubstitution,InterfaceIsolation,andDependencyInversion)是由RobertC.Martin在21世纪。前面介绍过,是指面向对象程序设计和面向对象设计的五个基本原则。当这些原则一起应用时,它们使程序员可以开发一个软件易于维护和扩展的系统。1.单一职责原则(SRP)对于一个类而言,它应该只有一个改变的理由。在JavaScript中,需要用到类的场景并不太多,单一职责原则更多的应用在对象或者方法层面。如果我们有两个动机来重写一个方法,那么这个方法就有两个责任。每一项责任都是变化的轴心。如果一个方法承担了太多的职责,那么在需求变化的过程中,这个方法就更有可能需要重写。简单地说:一个对象(方法)只做一件事。1.1优缺点优点:降低了单个类或对象的复杂度,将对象按照职责划分成更小的粒度,有利于代码复用和单元测试。缺点:增加编写代码的复杂度,对象被划分的粒度更小,对象之间的直接关系变得更复杂。2.开闭原则(OCP)开闭原则最早是由埃菲尔语言的设计者伯特兰迈耶在他的《面向对象的软件构造》一书中提出的。其定义如下:软件实体(类、模块、函数)等应该对扩展开放,对修改关闭。扩展window.onload函数Function.prototype.after=function(afterfn){var_self=this;returnfunction(){varret=_slef.apply(this,arguments);afterfn.apply(this,arguments);返还;}}window.onload=(window.onload||function(){}).after(function(){console.log("extendedcode")})通过动态装饰函数直接扩展新函数,而不是直接修改之前的onload相关代码。3、里氏??替换原则(LSP)定义如下:子类的设计应保证在替换父类时,不改变原程序的逻辑,不破坏原程序的正确性。示例://假设我们有一个使用矩形对象的程序,如下所示:varrectangle={length:0,width:0};//后来程序需要一个正方形,由于正方形是一个长度(length)和宽度(width)相同的特殊矩形,所以我们想到创建一个正方形而不是矩形。varsquare={};(function(){varlength=0,width=0;Object.defineProperty(square,"length",{get:function(){returnlength;},set:function(value){length=width=value;}});Object.defineProperty(square,"width",{get:function(){returnwidth;},set:function(value){length=width=value;}});})();不幸的是,当我们使用正方形而不是矩形来执行代码时,我们发现了一个问题。其中一种计算矩形面积的方法如下:varg=function(rectangle){rectangle.length=3;矩形.宽度=4;写(矩形。长度);写(矩形。宽度);写(矩形。长度*矩形。宽度);};当调用这个方法时,结果是16而不是预期的12,我们的正方形对象违反了LSP原则,正方形的长宽属性暗示它不是100%兼容矩形,但我们不它总是明确地暗示这一点。里氏代换原则(LiskovSubstitutionPrinciple,LSP)表达的意思不是继承关系,而是任何方法(只要方法的行为能理解其他行为)。4.接口隔离原则(ISP)的定义:不应该强迫客户依赖他们不使用的方法。客户不应该依赖他们不需要的接口。使用多个细粒度的接口来替代由多个方法组成的复杂接口。每个接口服务于一个子模块。A类通过interface接口依赖于C类,B类通过interface接口依赖于D类。如果interface接口是给A类的,不是给B类的最小接口(fatinterface),那么C类和D类必须实现自己不需要的方法。简单的说就是建立单一的专业接口,按照功能职责细化接口,尽量减少接口中的方法。示例:varrectangle={area:function(){/*code*/},draw:function(){/*code*/}};vargeometryApplication={getLargestRectangle:function(rectangles){/*code*/}};vardrawingApplication={drawRectangles:function(rectangles){/*code*/}};当一个矩形替换满足新对象geometryApplication的getLargestRectangle时,只需要rectangle的area()方法即可,但是违反了LSP(因为他不能使用只能由drawRectangles方法使用的draw方法)。5.依赖倒置原则(DIP)高层模块不应该依赖低层模块,两者都应该依赖于它的抽象。抽象不应该依赖于细节,细节应该依赖于抽象。依赖倒置原则最重要的问题是确保应用程序或框架的主要组件与不重要的低级组件的实现细节解耦。这将确保程序最重要的部分不会受到低级组件的更改和修改的影响。在JavaScript中,依赖倒置原则的适用范围仅限于高层模块和低层模块之间的语义耦合。例如,DIP可以根据需要添加接口,而不是耦合低级模块定义的隐式接口。$.fn.trackMap=function(options){vardefaults={/*defaults*/};options=$.extend({},defaults,options);varmapOptions={center:newgoogle.maps.LatLng(options.latitude,options.longitude),缩放:12,mapTypeId:google.maps.MapTypeId.ROADMAP},map=newgoogle.maps.Map(this[0],mapOptions),pos=newgoogle.maps.LatLng(options.latitude,options.longitude);varmarker=newgoogle.maps.Marker({position:pos,title:options.title,icon:options.icon});marker.setMap(地图);options.feed.update(function(latitude,longitude){marker.setMap(null);varnewLatLng=newgoogle.maps.LatLng(latitude,longitude);marker.position=newLatLng;marker.setMap(map);地图。setCenter(newLatLng);});returnthis;};varupdater=(function(){//私有属性return{update:function(callback){updateMap=回调;}};})();$("#map_canvas").trackMap({latitude:35.044640193770725,longitude:-89.98193264007568,icon:'http://bit.ly/zjnGDe',title:'TrackingNumber:12345',提要:更新器});在上面的代码中,有一个JS小类库,将一个DIV转换成一个Map来显示当前追踪到的位置信息。trackMap函数有两个依赖项:第三方GoogleMapsAPI和Locationfeed。feed对象的职责是在更新图标位置时调用回调(在初始化期间提供)并传入经纬度。GoogleMapsAPI用于呈现界面。feed对象的接口可以根据trackMap功能的要求设计也可以不设计。其实它的作用很简单,专注于简单的不同实现,不需要那么依赖GoogleMaps。因为trackMap与GoogleMapsAPI是语义耦合的,如果需要在不同的地图提供者之间切换,就不得不重写trackMap函数来适应不同的提供者。为了扭转谷歌地图类库的语义耦合,我们需要重写设计trackMap函数,在一个隐式接口上进行语义耦合(抽象出地图提供者provider的接口),同时我们也需要适配谷歌地图类库MapsAPI的一个实现对象,下面是重构后的trackMap函数:$.fn.trackMap=function(options){vardefaults={/*defaults*/};options=$.extend({},defaults,options);options.provider.showMap(this[0],options.latitude,options.longitude,options.icon,options.title);options.feed.update(function(latitude,longitude){options.provider.updateMap(latitude,longitude);});returnthis;};$("#map_canvas").trackMap({latitude:35.044640193770725,longitude:-89.98193264007568,icon:'http://bit.ly/zjnGDe',title:'TrackingNumber:12345',feed:更新程序,提供程序:trackMap.googleMapsProvider});在这个版本中,我们重新设计了trackMap功能和一个必需的地图提供者接口,然后将实现细节移到了一个单独的googleMapsProvider组件中,可以将其独立打包为一个单独的JavaScript模块。下面是我的googleMapsProvider实现:trackMap.googleMapsProvider=(function(){varmarker,map;return{showMap:function(element,latitude,longitude,icon,title){varmapOptions={center:newgoogle.maps.LatLng(纬度,经度),zoom:12,mapTypeId:google.maps.MapTypeId.ROADMAP},pos=newgoogle.maps.LatLng(latitude,longitude);map=newgoogle.maps.Map(element,mapOptions);标记=newgoogle.maps.Marker({position:pos,title:title,icon:icon});marker.setMap(map);},updateMap:function(latitude,longitude){marker.setMap(null);varnewLatLng=newgoogle.maps.LatLng(latitude,longitude);marker.position=newLatLng;marker.setMap(map);map.setCenter(newLatLng);}};})();做了上记经过这些改动之后,trackMap功能会变得非常灵活。它不需要依赖GoogleMapsAPI。相反,其他地图提供者可以任意替换,也就是说,任何地图提供者都可以根据程序的需要进行适配。
