当前位置: 首页 > Web前端 > JavaScript

JavaScript 中的 SOLID 原则(三):“L”代表什么

时间:2023-03-26 20:46:08 JavaScript

SOLIDPrinciplesinJavaScript(3):What"L"standsforWhatdoes"O"standforWhatdoes"O"standforWhatdoes"O"standforWhatdoes"O"standforWhatdoes"O"standforWhatdoes"O"standforWhatdoes"O"standforWhatdoes"O"standingfor这篇是SOLID的第三篇文章(原文有五篇),作者serhiirubets,欢迎继续打赏注意力。里氏替换原则(LiskovSubstitutionPrinciple)L-里氏替换原则。该原则意味着:如果S是T的子类型,则程序中的T对象可以被S对象替换,而无需更改程序中任何必需的属性。从定义上可能没有办法清楚地理解它的含义,我们换一种说法:使用指针或引用基类的函数必须可以被它们的派生类替换。//todo让我们用更简单的方式来描述它,例如:你有一个“Car”类,它被用在各个地方。这个原则意味着凡是用到Car类的地方都应该用Car类的子类代替。如果我们有一个继承自“Car”的“PassengerCar”,或者一个也继承自“Car”的“SUV”类,如果我们将“Car”类替换为“SUV”或“PassengerCar”,即After用任何子类替换父类Car,我们的系统应该像以前一样工作。举个简单的例子,我们有一个“Rectangle”(矩形)类,因为“square”也是“rectangle”,我们可以创建一个基本的“Rectangle”类和“Square”类,“Square”继承自“Rectangle”.classRectangle{constructor(width,height){this.width=widththis.height=height}setWidth(width){this.width=width}setHeight(height){this.height=height}getArea(){返回这个。width*this.height}}//Square计算面积的方式有点不同,它的高和宽是一样的,重写setWidth和setHeight方法。类Square扩展矩形{setWidth(width){this.width=width;this.height=宽度;}setHeight(height){this.width=height;this.height=高度;}}constrectangleFirst=newRectangle(10,15)constrectangleSecond=newRectangle(5,10)console.log(rectangleFirst.getArea());//150console.log(rectangleSecond.getArea());//50rectangleFirst.setWidth(20)rectangleSecond.setWidth(15)console.log(rectangleFirst.getArea());//300console.log(rectangleSecond.getArea());//150我们创建了两个实例,检查了矩形区域,更改了宽度和高度并再次检查了区域,我们看到现在一切正常,代码按预期工作,但是,让我们再看看里氏代换原则:如果我们更改任何子类的基类,我们的系统应该像以前一样工作。constrectangleFirst=newSquare(10,15)constrectangleSecond=newSquare(5,10)console.log(rectangleFirst.getArea());//150console.log(rectangleSecond.getArea());//50rectangleFirst.setWidth(20)rectangleSecond.setWidth(15)console.log(rectangleFirst.getArea());//400console.log(rectangleSecond.getArea());//225我们发现用newSquare()替换newRectangle()后,setWidth后getArea返回的值和替换前不一样,显然我们没有遵循里氏替换原则。那我们应该怎么解决呢?解决方案是使用继承,但不是从“Rectangle”类开始,而是准备一个更“正确”的类。例如,让我们创建一个只负责计算面积的“Sharp”类:classShape{getArea(){returnthis.width*this.height;}}classRectangle{constructor(width,height){super();这。width=widththis.height=height}setWidth(width){this.width=width}setHeight(height){this.height=height}}classSquareextendsShape{setWidth(width){this.width=width;这。高度=宽度;}setHeight(height){this.width=height;this.height=高度;我们创建了一个更通用的基类Shape,我们可以在不破坏原有逻辑的情况下将Shape修改为它的任何一个子类。在我们的示例中,Rectangle和Square是不同的对象,它们包含一些相似的逻辑,但也包含不同的逻辑,因此将它们分开而不是将它们用作“父子”类会更正确。让我们看另一个有助于理解这个原理的例子:我们要创建一个Bird类,我们正在考虑应该添加什么方法。从第一个角度来看,我们可以考虑加入fly方法,因为所有的鸟都会飞。在Bird{fly(){}}functionallFly(birds){birds.forEach(bird=>bird.fly())}allFly([newBird(),newBird(),newBird()])之后,我们意识到有不同的鸟类:鸭子、鹦鹉、天鹅。classDuckextendsBird{quack(){}}classParrotextendsBird{repeat(){}}classSwanextendsBird{beBeautiful(){}}现在,Liskov替换原则说如果我们将基类更改为子类,系统应该像以前一样工作:(bird=>bird.fly())}allFly([newDuck(),newParrot(),newSwan()])我们在调用allFly函数的时候改变了参数,我们调用了newDuck(),newParrot(),newSwan(),而不是调用newBird()。一切正常,我们正确地遵循了Liskov替换原则。现在我们想再添加一只企鹅,但是企鹅不会飞。如果我们想调用fly方法,我们会抛出一个错误。classPenguinextendsBird{fly(){thrownewError('抱歉,但我不会飞')}swim(){}}allFly([newDuck(),newParrot(),newSwan(),newPenguin()])但是我们有一个问题:fly方法不期望内部错误,而allFly方法只是为会飞的鸟创建的,企鹅不会飞,所以我们违反了Liskov替换原则。如何解决这个问题呢?与其创建一个基本的Bird类,不如创建一个FlyingBird类。所有飞鸟只继承自FlyingBird类,allFly方法只接受FlyingBird。类Bird{}类FlyingBird{fly(){}}类Duck扩展FlyingBird{嘎嘎(){}}类Parrot扩展FlyingBird{repeat(){}}类Swan扩展FlyingBird{beBeautiful(){}}类Penguin扩展鸟{swim(){}}Penguin继承自Bird类,而不是FlyingBird类,我们不需要调用会抛出错误的fly方法。在任何调用FlyingBird的地方,只需将其替换为更具体的鸟,例如Duck、Parrot、Swan,代码就可以正常工作。希望你能通过本文更好地理解里氏代换原理,理解它在JavaScript中的工作原理以及如何在项目中使用它。下一篇我们继续学习SOLID中的下一个'L'字母欢迎关注微信公众号《混沌前端》推荐阅读:SOLID原理基于理解程序设计用TypeScriptclean-code-javascript:SOLID