设计准则(SOLID准则)
在程序设计畛域, SOLID(繁多性能、开闭准则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪晚期引入,指代了面向对象编程和面向对象设计的五个根本准则。当这些准则被一起利用时,它们使得一个程序员开发一个容易进行软件维护和扩大的零碎变得更加可能。
1. 繁多职责准则(SRP)
就一个类而言,应该仅有一个引起它变动的起因。在JavaScript中,须要用到类的场景并不太多,繁多职责准则更多地是被使用在对象或者办法级别上。
如果咱们有两个动机去改写一个办法,那么这个办法就具备两个职责。每个职责都是变动的一个轴线,如果一个办法承当了过多的职责,那么在需要的变迁过程中,须要改写这个办法的可能性就越大。
简略说就是:一个对象(办法)只做一件事。
1.1 优缺点
- 长处: 升高了单个类或对象复杂度,依照职责把对象划分为更小粒度,有利于代码复用和单元测试。
- 毛病: 减少编写代码复杂度,对象划分为更小粒度,对象间接的分割也变得更简单。
2. 凋谢-关闭准则(OCP)
凋谢-关闭准则最早由Eiffel语言的设计者Bertrand Meyer在其著述Object-Oriented Software Construction中提出。它的定义如下:
软件实体(类、模块、函数)等应该对扩大凋谢,然而批改关闭。
拓展window.onload函数
Function.prototype.after = function(afterfn) { var _self = this; return function() { var ret = _slef.apply(this, arguments); afterfn.apply(this, arguments); return ret; }}window.onload = (window.onload || function() {}).after(function(){ consolo.log("拓展的代码")})
通过动静装璜函数的形式,间接拓展了新的函数,而不是间接批改之前的onload相干代码。
3. 里氏替换准则(The Liskov Substitution Principle,LSP)
定义如下:
子类的设计要保障在替换父类的时候,不扭转原有程序的逻辑以及不毁坏原有程序
的正确性
矩形例子:
// 思考咱们有一个程序用到上面这样的一个矩形对象:var rectangle = { length: 0, width: 0};// 过后,程序有须要一个正方形,因为正方形就是一个长(length)和宽(width)都一样的非凡矩形,所以咱们感觉创立一个正方形代替矩形。var square = {};(function() { var length = 0, width = 0; Object.defineProperty(square, "length", { get: function() { return length; }, set: function(value) { length = width = value; } }); Object.defineProperty(square, "width", { get: function() { return width; }, set: function(value) { length = width = value; } });})();可怜的是,当咱们应用正方形代替矩形执行代码的时候发现了问题,其中一个计算矩形面积的办法如下:var g = function(rectangle) { rectangle.length = 3; rectangle.width = 4; write(rectangle.length); write(rectangle.width); write(rectangle.length * rectangle.width);};
该办法在调用的时候,后果是16,而不是冀望的12,咱们的正方形square对象违反了LSP准则,square的长度和宽度属性暗示着并不是和矩形100%兼容,但咱们并不总是这样明确的暗示。
里氏替换准则(LSP)表白的意思不是继承的关系,而是任何办法(只有该办法的行为能领会另外的行为就行)。
4. 接口隔离准则(ISP)
定义:
Clients should not be forced to depend on methods they do not use.
客户端不应该依赖它不须要的接口。用多个细粒度的接口来代替由多个办法组成的简单接口,每个接口服务于一个子模块
类A通过接口interface依赖类C,类B通过接口interface依赖类D,如果接口interface对于类A和类B来说不是最小接口(胖接口),则类C和类D必须去实现他们不须要的办法。
简略说就,建设繁多业余接口,依照性能职责细化接口,接口中的办法尽量少。
例子:
var rectangle = { area: function() { /* 代码 */ }, draw: function() { /* 代码 */ }};var geometryApplication = { getLargestRectangle: function(rectangles) { /* 代码 */ }};var drawingApplication = { drawRectangles: function(rectangles) { /* 代码 */ }};
当一个rectangle替代品为了满足新对象geometryApplication的getLargestRectangle 的时候,它仅仅须要rectangle的area()办法,但它却违反了LSP(因为他基本用不到其中drawRectangles办法能力用到的draw办法)。
5. 依赖倒置准则(Dependence Inversion Principle, DIP)
高层模块不应该依赖底层模块,二者都该依赖其形象。形象不应该依赖细节,细节应该依赖形象。
依赖倒置准则的最重要问题就是确保应用程序或框架的次要组件从非重要的底层组件实现细节解耦进去,这将确保程序的最重要的局部不会因为低层次组件的变动批改而受影响。
在JavaScript里,依赖倒置准则的适用性仅仅限于高层模块和低层模块之间的语义耦合,比方,DIP能够依据须要去减少接口而不是耦合低层模块定义的隐式接口。
$.fn.trackMap = function(options) { var defaults = { /* defaults */ }; options = $.extend({}, defaults, options); var mapOptions = { center: new google.maps.LatLng(options.latitude,options.longitude), zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP }, map = new google.maps.Map(this[0], mapOptions), pos = new google.maps.LatLng(options.latitude,options.longitude); var marker = new google.maps.Marker({ position: pos, title: options.title, icon: options.icon }); marker.setMap(map); options.feed.update(function(latitude, longitude) { marker.setMap(null); var newLatLng = new google.maps.LatLng(latitude, longitude); marker.position = newLatLng; marker.setMap(map); map.setCenter(newLatLng); }); return this;};var updater = (function() { // private properties return { update: function(callback) { updateMap = callback; } };})();$("#map_canvas").trackMap({ latitude: 35.044640193770725, longitude: -89.98193264007568, icon: 'http://bit.ly/zjnGDe', title: 'Tracking Number: 12345', feed: updater});
在上述代码里,有个小型的JS类库将一个DIV转化成Map以便显示以后跟踪的地位信息。trackMap函数有2个依赖:第三方的Google Maps API和Location feed。该feed对象的职责是当icon地位更新的时候调用一个callback回调(在初始化的时候提供的)并且传入纬度latitude和精度longitude。Google Maps API是用来渲染界面的。
feed对象的接口可能依照装,也可能没有照装trackMap函数的要求去设计,事实上,他的角色很简略,着重在简略的不同实现,不须要和Google Maps这么依赖。介于trackMap语义上耦合了Google Maps API,如果须要切换不同的地图提供商的话那就不得不对trackMap函数进行重写以便能够适配不同的provider。
为了将于Google maps类库的语义耦合翻转过去,咱们须要重写设计trackMap函数,以便对一个隐式接口(形象出地图提供商provider的接口)进行语义耦合,咱们还须要一个适配Google Maps API的一个实现对象,如下是重构后的trackMap函数:
$.fn.trackMap = function(options) { var defaults = { /* 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); }); return this;};$("#map_canvas").trackMap({ latitude: 35.044640193770725, longitude: -89.98193264007568, icon: 'http://bit.ly/zjnGDe', title: 'Tracking Number: 12345', feed: updater, provider: trackMap.googleMapsProvider});
在该版本里,咱们从新设计了trackMap函数以及须要的一个地图提供商接口,而后将实现的细节挪到了一个独自的googleMapsProvider组件,该组件可能独立封装成一个独自的JavaScript模块。如下是我的googleMapsProvider实现:
trackMap.googleMapsProvider = (function() { var marker, map; return { showMap: function(element, latitude, longitude, icon, title) { var mapOptions = { center: new google.maps.LatLng(latitude, longitude), zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP }, pos = new google.maps.LatLng(latitude, longitude); map = new google.maps.Map(element, mapOptions); marker = new google.maps.Marker({ position: pos, title: title, icon: icon }); marker.setMap(map); }, updateMap: function(latitude, longitude) { marker.setMap(null); var newLatLng = new google.maps.LatLng(latitude,longitude); marker.position = newLatLng; marker.setMap(map); map.setCenter(newLatLng); } };})();
做了上述这些扭转当前,trackMap函数将变得十分有弹性了,不用依赖于Google Maps API,相同能够任意替换其它的地图提供商,那就是说能够依照程序的需要去适配任何地图提供商。