本文转载自微信公众号“大千世界”,转载请联系大千世界公众号。设计模式的六大原则是:SingleResponsibilityPrinciple:单一职责原则OpenClosedPrinciple:开闭原则LiskovSubstitutionPrinciple:里氏代换原则LawofDemeter:Dimit'sLawInterfaceSegregationPrinciple:接口隔离原则DependenceInversionPrinciple:依赖性inversionprinciple结合了这六个原则的首字母(两个L算一个)是SOLID(solid,stable),意思是结合这六个原则的好处:建立稳定、灵活、稳健的设计。让我们来看看这六个设计原则中的每一个。单一职责原则(SRP)单一功能原则:单一功能原则是指一个对象应该只有一个单一功能的概念。也就是说,让一个类只做一种类型的职责。当这个类需要承担其他类型的职责时,需要分解这个类。在所有SOLID原则中,这是大多数开发人员认为理解最充分的原则。严格来说,这可能也是最常被违反的原则。单一职责原则可以看作是面向对象原则上低耦合高内聚的延伸,将职责定义为变化的原因,提高内聚以减少变化的原因。如果职责太多,其变化的原因就会更多,从而导致职责依赖和相互影响,从而极大地破坏其内聚性和耦合性。单一职责通常意味着单一的功能,所以不要为一个模块实现太多的功能点,以保证实体只有一个改变的原因。“糟糕的写作方式”classUserSettings{constructor(user){this.user=user;}changeSettings(settings){if(this.verifyCredentials()){//...}}verifyCredentials(){//...}}“好文”user);}changeSettings(settings){if(this.auth.verifyCredentials()){//...}}}开闭原则(OCP)软件实体应该是可扩展的,不可修改的。也就是说,它对扩展开放但对修改关闭。这个原理是众多面向对象编程原理中最抽象、最难理解的。通过添加代码而不是修改现有代码来扩展功能。如果客户端模块和服务模块按照相同的接口设计,客户端模块不需要关心服务模块的类型,服务模块可以很容易地扩展服务(代码)。OCP在不修改客户端模块的情况下支持备用服务。说白了:你不想改变吗?那我就让你继承实现一个对象,用一个接口来抽象你的职责。你改变的越多,你继承和实现的子类就越多。「不好的写法」classAjaxAdapterextendsAdapter{constructor(){super();this.name="ajaxAdapter";}}classNodeAdapterextendsAdapter{constructor(){super();this.name="nodeAdapter";}}classHttpRequester{constructor(适配器){this.adapter=adapter;}fetch(url){if(this.adapter.name==="ajaxAdapter"){returnmakeAjaxCall(url).then(response=>{//transformresponseandreturn});}elseif(this.adapter.name==="nodeAdapter"){returnmakeHttpCall(url).then(response=>{//transformresponseandreturn});}}}functionmakeAjaxCall(url){//requestandreturnpromise}functionmakeHttpCall(url){//requestandreturnpromise}「好的写法」classAjaxAdapterextendsAdapter{constructor(){super();this.name="ajaxAdapter";}request(url){//requestandreturnpromise}}classNodeAdapterextendsAdapter{constructor(){super();this.name="nodeAdapter";}request(url){//requestandreturnpromise}}classHttpRequester{constructor(adapter){this.adapter=adapter;}fetch(url){returnthis.adapter.request(url).then(response=>{//transformresponseandreturn});}}LiskovSubstitutionPrinciple(LSP)里氏替换原则:里氏替换原则认为“程序中的对象应该被”的概念替换为它的子类在正确性的前提下”为我们判断和设计类之间的关系提供了依据:是否需要继承,以及如何设计继承关系。子类的实例之间应该可以时存在is-A关系替换它的任何超类的实例。继承是“OCP”,多态性是Liskov替换原则。子类可以替换基类,客户使用基类,他们不需要知道派生类的作用.这是行为责任的可替换原则。如果S是T的子类型,那么S对象应该替换所有T对象而不改变任何抽象属性。客户端模块不应该关心关于服务模块如何工作;在不知道服务模块代码的情况下,可以更换相同的接口模块。即接口或父类出现的地方,可以用实现该接口的类或子类代替。「不好的写法」classRectangle{constructor(){this.width=0;this.height=0;}setColor(color){//...}render(area){//...}setWidth(width){this.width=width;}setHeight(height){this.height=height;}getArea(){returnthis.width*this.height;}}classSquareextendsRectangle{setWidth(width){this.width=width;this.height=width;}setHeight(height){this.width=height;this.height=height;}}functionrenderLargeRectangles(rectangles){rectangles.forEach(rectangle=>{rectangle.setWidth(4);rectangle.setHeight(5);constarea=rectangle.getArea();//BAD:Returns25forSquare.Shouldbe20.rectangle.render(area);});}constrectangles=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles(rectangles);「好的写法”classShape{setColor(color){//...}render(area){//...}}classRectangleextendsShape{constructor(width,height){super();this.width=width;this.height=height;}getArea(){returnthis.width*this.height;}}classSquareextendsShape{constructor(length){super();this.length=length;}getArea(){returnthis.length*this.length;}}functionrenderLargeShapes(shapes){shapes.forEach(shape=>{constarea=shape.getArea();shape.render(area);});}constshapes=[newRectangle(4,5),newRectangle(4,5),新方块(5)];renderLargeShapes(形状);InterfaceSegregationPrinciple(ISP)接口隔离原则:接口隔离原则声明“多个特定于客户端的接口优于一个通用接口”这一概念不能强制执行用户依赖于他们不使用的接口。换句话说,使用多个专用接口总是比使用一个通用接口更好。这个原理起源于施乐公司,他们需要构建一个新的打印机系统,可以执行多项任务,例如装订一组打印件和传真。系统软件是从底层创建并实现这些任务功能的,但是不断增加的软件功能使得软件本身越来越难以适应变化和维护。每次做出更改,即使是最小的更改,也可能需要将近一个小时的重新编译和重新部署。继续开发几乎是不可能的,所以他们聘请了罗伯特来帮助他们。他们首先设计了一个主类Job,可以用来实现几乎所有的任务功能。只要调用Job类的一个方法就可以实现一个功能,Job类变化很大。这是一个胖模型。如果客户端只需要一个打印功能,而其他与打印无关的方法和功能也与之耦合,ISP原则建议在客户端和Job类之间增加一个接口层。不同的功能有不同的接口。比如打印功能就是Print接口,然后把大的Job类分成继承不同接口的子类,这样就有了PrintJob类,ETC。“糟糕的写作方式”(){//...}}const$=newDOMTraverser({rootNode:document.getElementsByTagName("body"),animationModule(){}//大多数时候,我们不需要在遍历时设置动画。//...});"classDOMTraverser{constructor(settings){this.settings=settings;this.options=settings.options;this.setup();}setup(){thisthis.rootNode=this.settings.rootNode;this.setupOptions的写法();}setupOptions(){if(this.options.animationModule){//...}}traverse(){//...}}const$=newDOMTraverser({rootNode:document.getElementsByTagName("body"),options:{animationModule(){}}});DependencyInversionPrinciple(DIP)DependencyInversionPrinciple:依赖倒置原则认为一个方法应该遵循“依赖于抽象而非实例”的概念。依赖注入是该原则的一种实现。依赖倒置原则(DIP)规定代码应该依赖抽象概念,而不是具体实现。高层模块不依赖低层模块高层和低层模块都依赖抽象;抽象不依赖于具体实现具体实现依赖于抽象抽象离子和接口可以分离模块之间的依赖关系类可能依赖于其他类来执行它们的工作。但是,它们不应该依赖于类的特定具体实现,而是依赖于类的抽象。这个原则真的很重要。社会分工和标准化是这一设计原则的体现。显然,这个概念会大大增加系统的灵活性。如果这些类只关心它们用来支持特定契约而不是特定类型的组件,那么可以快速轻松地修改这些低级服务的功能,而对系统其余部分的影响最小。「不好的写法」classInventoryRequester{constructor(){this.REQ_METHODS=["HTTP"];}requestItem(item){//...}}classInventoryTracker{constructor(items){this.items=items;//错误:Wehavecreatedadedependencyonaspecificrequestimplementation.//WeshouldjustthaverequestItemsdependonarequestmethod:`request`this.requester=newInventoryRequester();}requestItems(){this.items.forEach(item=>{this.requester.requestItem(item);});}}constinventoryTracker=newInventoryTracker(["apples","bananas"]);inventoryTracker.requestItems();「好的写法」classInventoryTracker{constructor(items,requester){this.items=items;this.requester=requester;}requestItems(){this.items.forEach(item=>{this.requester.requestItem(item);});}}classInventoryRequesterV1{constructor(){this.REQ_METHODS=["HTTP"];}requestItem(item){//...}}classInventoryRequesterV2{constructor(){this.REQ_METHODS=["WS"];}requestItem(item){//...}}constinventoryTracker=newInventoryTracker([";apples","bananas"],newInventoryRequesterV2());inventoryTracker.requestItems();
