当前位置: 首页 > 科技观察

理解基于TypeScript编程的SOLID原则

时间:2023-03-21 21:53:07 科技观察

大家好,我是ConardLi,今天我们来回顾和学习基于TypeScript编程的SOLID原则。说起SOLID原则,可能写过代码的同学应该都听说过。这是编程领域最常用的设计原则。SOLID由RobertC.Martin在21世纪初提出,指的是面向对象编程和面向对象设计的五个基本原则。SOLID其实是以下五个词的缩写:SingleResponsibilityPrinciple:单一职责原则OpenClosedPrinciple:LiskovSubstitutionPrinciple:里氏替换原则InterfaceSegregationPrinciple:接口隔离原则DependencyInversionPrinciple:依赖倒置原则TypeScript的出现让我们可以用面向对象的思想写出更简洁的JavaScript代码。在下面的文章中,我们将用TypeScript编写一些示例来解释这些原则中的每一个。单一职责原则(SRP)的核心思想:一个类应该有单一的职责,不能承担过多的职责。我们先来看看下面的代码。我们为Book创建了一个类,但是这个类有多个职责,比如将书保存为文件:classBook{publictitle:string;公共作者:字符串;公共描述:字符串;公共页面:数量;//构造函数和其他方法publicsaveToFile():void{//一些fs.write方法将书保存到文件}}按照单一职责的原则,我们应该创建两个类负责不同的事物:classBook{publictitle:细绳;公共作者:字符串;公共描述:字符串;公共页面:数量;//构造函数和其他方法}classPersistence{publicsaveToFile(book:Book):void{//一些fs.write方法将书保存到文件}}好处:降低类的复杂性,提高可读性、可维护性、可扩展性,并最大限度地减少潜在副作用。开闭原则(OCP)的核心思想:一个类应该对扩展开放,对修改关闭。简单理解就是当别人要修改软件功能的时候,我们不能让他修改我们原来的代码,要尽量让他在原来的基础上进行扩展。让我们看看下面写得不好的代码。我们分别封装了一个AreaCalculator类来计算Rectangle和Circle类的面积。想象一下,如果我们后面要再添加一个形状,就得新建一个类,还要修改AreaCalculator来计算新类的面积,这就违反了开闭原则。类矩形{公共宽度:数字;公共高度:数字;构造函数(宽度:数字,高度:数字){this.width=width;this.height=高度;}}classCircle{公共半径:数字;构造函数(半径:数字){this.radius=radius;}}classAreaCalculator{publiccalculateRectangleArea(rectangle:Rectangle):number{returnrectangle.width*rectangle.height;}publiccalculateCircleArea(circle:Circle):number{returnMath.PI*(circle.radius*circle.radius);}}为了遵循开闭原则,我们只需要添加一个名为Shape的接口,每个形状类(矩形、圆形等)都可以通过实现它来依赖它。这样,我们就可以将AreaCalculator类简化为一个带参数的函数,每当我们创建一个新的shape类时都必须执行这个函数,这样就不需要修改原来的类:interfaceShape{calculateArea():number;}类Rectangle实现Shape{publicwidth:number;公共高度:数字;构造函数(宽度:数字,高度:数字){this.width=width;this.height=高度;}publiccalculateArea():number{returnthis.width*this.height;}}classCircle实现Shape{publicradius:number;构造函数(半径:数字){this.radius=radius;}publiccalculateArea():number{returnMath.PI*(this.radius*this.radius);}}classAreaCalculator{publiccalculateArea(shape:Shape):number{returnshape.calculateArea();}}里氏替换原则(LSP)核心思想:基类用在什么地方其子类可以任意使用,可以保证子类完美替代基类。简单理解就是,父类能出现的地方,子类就可以出现,替换了就不会出错。我们必须要求子类所有相同的方法必须遵循父类的约定,否则父类被子类替换时会出错。先看下面的代码,Square类扩展了Rectangle类。但是这个扩展没有任何意义,因为我们通过覆盖width和height属性改变了原来的逻辑。类矩形{公共宽度:数字;公共高度:数字;构造函数(宽度:数字,高度:数字){this.width=width;this.height=高度;}publiccalculateArea():number{returnthis.width*this.height;}}classSquareextendsRectangle{public_width:number;public_height:数字;构造函数(宽度:数字,高度:数字){超级(宽度,高度);this._width=宽度;这个._height=高度;}}遵循里氏代换原则,我们不需要重写基类的属性,直接删除Square类,将其逻辑带入Rectangle类,不改变其用途。类矩形{公共宽度:数字;公共高度:数字;构造函数(宽度:数字,高度:数字){this.width=width;this.height=高度;}publiccalculateArea():number{returnthis.width*this.height;}publicisSquare():boolean{returnthis.width===this.height;}}好处:增强程序的健壮性,即使添加子类,原有的子类也能继续运行。接口隔离原则(ISP)的核心思想:类之间的依赖应该建立在最小的接口上。简单的理解就是界面的内容一定要尽可能小,越小越好。我们需要为每个类创建一个专用的接口,而不是试图为所有依赖它的类创建一个非常大的接口来调用。看看下面的代码,我们有一个名为Troll的类实现了一个名为Character的接口,但是Troll既不会游泳也不会说话,所以它似乎不太适合实现我们的接口:interfaceCharacter{shoot():void;游泳():无效;谈话():无效;dance():void;}classTrollimplementsCharacter{publicshoot():void{//一些方法}publicswim():void{//巨魔不会游泳}publictalk():void{//atrollcan'ttalk}publicdance():void{//somemethod}}遵循接口隔离的原则,我们把Character接口删掉,取而代之功能拆分成四个接口,然后我们的Troll类只需要依赖于我们实际需要的那些接口。interfaceTalker{talk():void;}interfaceShooter{shoot():void;}interfaceSwimmer{swim():void;}interfaceDancer{dance():void;}classTrollimplementsShooter,Dancer{publicshoot():void{//somemethod}publicdance():void{//somemethod}}依赖倒置原则(DIP)核心思想:依赖抽象的服务接口,而不是依赖具体的服务执行者,来自Relyingonconcrete实现转向依赖抽象接口,反之亦然。看看下面的代码,我们有一个初始化FrontendDeveloper和BackendDeveloper类的SoftwareProject类:方法}}classSoftwareProject{publicfrontendDeveloper:FrontendDeveloper;公共后端开发人员:后端开发人员;constructor(){this.frontendDeveloper=newFrontendDeveloper();this.backendDeveloper=newBackendDeveloper();writeHtmlCode();this.backendDeveloper.writeTypeScriptCode();}}遵循依赖倒置的原则,我们创建一个Developer接口。由于FrontendDeveloper和BackendDeveloper是相似的类,它们都依赖于Developer接口。我们不是在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();});}}好处:实现了模块之间的松耦合,更有利于多个模块的并行开发。