-DesignPrinciples(SOLID)1.S-单一职责原则(SingleResponsibilityPrinciple)1.1定义一个类或模块,只负责完成一个职责(或功能),认为“对象应该只有一个功能”。如果一个类中包含两个或多个与业务无关的功能,则认为职责不够单一,可以将单一功能拆分为多个类。1.2举个栗子Employee类包含了很多不同的行为,违反了单责原则。通过拆分TimeSheetReport类,依赖Employee类,遵循单责原则2.O——开闭原则(Open-ClosedPrinciple)2.1定义软件实体(包括类、模块、函数等)应该是opentoextension,butclosedtomodification,满足以下两个特征的opentoextension模块是opentoextension的,意思是当需求发生变化时,可以通过扩展模块来满足那些变化新的行为是对修改关闭的。该模块因修改而关闭。意思是当需求发生变化时,尽量在不修改源代码的情况下扩展功能,如果以后增加新的shippingmethod,需要修改Order原来的方法getShippingCost(),这违背了OCP中多态的思想。可以把shipping抽象成一个类,后面在不修改原来的Order类的情况下增加shipping方法。3.L-里氏代换原则(LiskovSubstitutionPrinciple)3.1定义使用父类的地方可以用子类代替,子类可以兼容父类子类方法的参数类型应该更抽象或者更宽比父方法的参数类型。子类方法的返回值类型应该比父类方法的返回值类型更具体或更窄。3.2举个栗子子类方法的例子参数类型要比父类方法的参数类型更抽象或者更宽democlassAnimal{}classCatextendsAnimal{favoriteFood:string;构造函数(faviroteFood:字符串){超级();this.faviroteFood=favoriteFood;}}classBreeder{feed(c:Animal){console.log("Breederfeedanimal");}}classCatCafeextendsBreeder{feed(c:Animal){console.log("CatCafefeedanimal");}constanimal=newAnimal();constbreeder=newBreeder();breeder.feed(animal);//约束子类能够接受父类输入constcatCafe=newCatCafe();catCafe.feed(animal);子类方法的返回值类型应该比父类方法的返回值类型更具体或更窄classAnimal{}classCatextendsAnimal{favoriteFood:string;构造函数(faviroteFood:字符串){超级();this.faviroteFood=faviroteFood;}}classBreeder{buy():Animal{returnnewAnimal();}}classCatCafeextendsBreeder{buy():Cat{returnnewCat("");}}constbreeder=newBreeder();leta:Animal=breeder.buy();constcatCafe=newCatCafe();a=catCafe.buy();子类不应该加强前置条件子类不应该削弱后置条件4.I-接口隔离原则(InterfaceSegregationPrinciple)4.1定义了客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小接口4.2比如类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I不是类A和类B的最小接口,那么B类和D类必须实现它们不需要的方法interfaceI{m1():void;m2():无效;m3():无效;m4():无效;m5():void;}classBimplementsI{m1():void{}m2():void{}m3():void{}//冗余方法实现m4():void{}//冗余方法实现m5():void{}}classA{m1(i:I):void{i.m1();}m2(i:I):void{i.m2();}m3(i:I):void{i.m3();}}classDimplementsI{m1():void{}//冗余方法实现m2():void{}//冗余方法实现m3():void{}m4():void{}m5():void{}}C类{m1(i:I):void{i.m1();}m4(i:I):void{i.m4();}m5(i:I):void{i.m5();}}将臃肿的接口I拆分成几个独立的接口,A类和C类分别与自己需要的接口建立依赖关系interfaceI{m1():void;}interfaceI2{m2():void;m3():void;}interfaceI3{m4():void;m5():void;}B类实现I,I2{m1():void{}m2():void{}m3():void{}}classA{m1(i:I):void{i.m1();}m2(i:I2):void{i.m2();}m3(i:I2):void{i.m3();}}D类实现I、I3{m1():void{}m4():void{}m5():void{}}classC{m1(i:I):void{i.m1();}m4(i:I3):void{i.m4();}m5(i:I3):void{i.m5();}}4.3实战栗子以电动自行车为例普通电动自行车不具备定位查看历史行程的功能,但是既然已经实现了ElectricBicycle接口,那么接口中不需要的方法就要实现。更好的方法是Split5.D-DependencyInversionPrinciple5.1定义依赖一个抽象的服务接口,而不是依赖具体的服务执行者,从依赖具体实现转向依赖抽象接口,倒置在软件设计中可以分为类有两个层次:高层模块,低层模块,高层模块不应该依赖低层模块,两者都应该依赖于它的抽象。高层模块是指调用者,低层模块是指一些基本操作。依赖倒置是基于这样一个事实,即与实现细节的可变性相比,抽象内容更稳定。5.2比如SoftwareProject类直接依赖了两个低层类FrontendDeveloper和BackendDeveloper,此时又来了一个新的低层模块,需要修改高层模块的依赖SoftwareProjectclassFrontendDeveloper{publicwriteHtmlCode():void{//一些方法}}classBackendDeveloper{publicwriteTypeScriptCode():void{//一些方法}}classSoftwareProject{publicfrontendDeveloper:FrontendDeveloper;公共后端开发人员:后端开发人员;constructor(){this.frontendDeveloper=newFrontendDeveloper();this.backendDeveloper=newBackendDeveloper();}publiccreateProject():void{this.frontendDeveloper.writeHtmlCode();this.backendDeveloper.writeTypeScriptCode();}}可以遵循依赖倒置原则,由于FrontendDeveloper和BackendDeveloper是类似的类,我们可以抽象出一个develop接口让FrontendDeveloper和BackendDeveloper实现,我们不需要在SoftwareProject类中单独初始化FrontendDeveloper和BackendDeveloper,但将它们作为列表遍历并调用每个develop()方法interfaceDeveloper{develop():void;}classFrontendDeveloperimplementsDeveloper{publicdevelop():void{this.writeHtmlCode();}privatewriteHtmlCode():void{//一些方法}}classBackendDeveloperimplementsDeveloper{publicdevelop():void{this.writeTypeScriptCode();}privatewriteTypeScriptCode():void{//一些方法}}classSoftwareProject{publicdevelopers:Developer[];publiccreateProject():void{this.developers.forEach((developer:Developer)=>{developer.develop();});}}第二种访问者模式1.意在表示一个作用于对象结构中每个元素的操作它允许您在这些元素上定义新操作,而无需更改每个元素的类。它允许您在这些元素上定义新操作,而无需更改每个元素的类。也就是说,你只能在Visitor本身修改新操作的定义,而不能修改原来的对象。Visitor的绝妙设计在于,就是把对象的操作权交给了Visitor2。场景如果需要对复杂对象结构(如对象树)中的所有元素进行某些操作,可以使用访问者模式。每个目标类都提供了相同操作的变体,允许你对属于不同类的一组对象执行相同的操作3.访问者模式结构Visitor:访问者接口ConcreteVisitor:特定访问者元素:可以被访问者元素使用,必须定义接收访问者对象的Accept属性。这是实现访客模式的关键。可以看出,要实现将操作权转移给Visitor,核心是元素必须实现一个Accept函数,将这个对象丢给Visitor:classConcreteElementimplementsElement{publicaccept(visitor:Visitor){.visit(this)}}从上面的代码中我们可以看到这样一个环节:Element通过accept函数接收到Visitor对象,将自己的实例抛给Visitor的visit函数,这样我们就可以在Visitor的visit中获取到方法。进入对象实例,完成对对象的操作4.实现方法及伪代码本例中访问者模式为几何图像层级添加对XML文件导出功能的支持4.1在访问者中声明一组“访问”接口方法,对应程序接口Visitor中的每个具体元素类{visitDot(d:Dot):void;visitCircle(c:Circle):void;visitRectangle(r:Rectangle):void;}4.2声明元素接口。如果程序中已经存在元素类层次结构接口,则可以在层次结构基类中添加一个抽象的“receive”方法。该方法必须接受访问者对象作为参数interfaceShape{accept(v:Visitor):void;}4.3在所有具体元素类中实现接收方法,元素类只能通过访问者接口与访问者进行交互,但是visitor必须知道所有具体的元素类,因为这些类在visitor方法中作为参数类型被引用publicaccept(v:Visitor):void{returnv.visitCircle(this)}}classRectangleimplementsShape{publicaccept(v:Visitor):void{returnv.visitRectangle(this)}}4.4创建具体的visitor类和实现所有访问者方法}visitCircle(c:Circle):void{console.log(`导出圆的ID、圆心坐标和半径`);}visitRectangle(r:Rectangle):void{console.log(`导出ID、左上角坐标、长方形的宽长`);}}4.5客户端必须创建一个访问者对象并通过“接收”方法将其传递给元素constapplication=(shapes:Shape[],visitor:Visitor)=>{//…for(constshapeofallShapes){形状。接受(访客);}//...}constallShapes=[newDot(),newCircle(),newRectangle()];constxmlExportVisitor=newXMLExportVisitor();应用程序(allShapes,xmlExportVisitor);4.6完整代码预览界面Visitor{visitDot(d:Dot):void;visitCircle(c:Circle):void;visitRectangle(r:Rectangle):void;}interfaceShape{accept(v:Visitor):void;}classDot实现Shape{publicaccept(v:Visitor):void{returnv.visitDot(this)}}classCircle实现Shape{publicaccept(v:Visitor):void{returnv.visitCircle(this)}}classRectangleimplementsShape{publicaccept(v:Visitor):void{returnv.visitRectangle(this)}}classXMLExportVisitorimplementsVisitor{visitDot(d:Dot):void{console.log(`导出点(点)ID和中心坐标`);}visitCircle(c:Circle):void{console.log(`导出圆的ID、圆心坐标和半径`);}visitRectangle(r:Rectangle):void{console.log(`导出ID、左上角坐标、长方形的宽长`);}}constallShapes=[newDot(),newCircle(),newRectangle()];constapplication=(shapes:Shape[],visitor:Visitor)=>{//......for(constshapeofallShapes){形状.accept(访客);//.....}constxmlExportVisitor=newXMLExportVisitor();应用程序(allShapes,xmlExportVisitor);5、访问者模式的优缺点:开闭原则可以在不同类型的对象上引入执行单一职责原则可以把同一个行为的不同版本移到同一个类中不足:每次添加一个类或者从元素层次结构中移除,你必须更新所有当访问者与元素交互时,他们可能没有必要的权限来访问元素的私有成员变量和方法
