文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何写出优雅的 JS 代码?使用 SOLID 原则

2024-12-24 17:16

关注

设计模式的六大原则有:

把这六个原则的首字母联合起来(两个 L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。下面我们来分别看一下这六大设计原则。

[[326031]]

单一职责原则(SRP)

单一功能原则 :单一功能原则 认为对象应该仅具有一种单一功能的概念。

换句话说就是让一个类只做一种类型责任,当这个类需要承担其他类型的责任的时候,就需要分解这个类。在所有的SOLID原则中,这是大多数开发人员感到最能完全理解的一条。严格来说,这也可能是违反最频繁的一条原则了。单一责任原则可以看作是低耦合、高内聚在面向对象原则上的引申,将责任定义为引起变化的原因,以提高内聚性来减少引起变化的原因。责任过多,可能引起它变化的原因就越多,这将导致责任依赖,相互之间就产生影响,从而极大的损伤其内聚性和耦合度。单一责任,通常意味着单一的功能,因此不要为一个模块实 现过多的功能点,以保证实体只有一个引起它变化的原因。

「不好的写法」

  1. class UserSettings { 
  2.   constructor(user) { 
  3.     this.user = user; 
  4.   } 
  5.  
  6.   changeSettings(settings) { 
  7.     if (this.verifyCredentials()) { 
  8.       // ... 
  9.     } 
  10.   } 
  11.  
  12.   verifyCredentials() { 
  13.     // ... 
  14.   } 

「好的写法」

  1. class UserAuth { 
  2.   constructor(user) { 
  3.     this.user = user; 
  4.   } 
  5.  
  6.   verifyCredentials() { 
  7.     // ... 
  8.   } 
  9.  
  10. class UserSettings { 
  11.   constructor(user) { 
  12.     this.user = user; 
  13.     this.auth = new UserAuth(user); 
  14.   } 
  15.  
  16.   changeSettings(settings) { 
  17.     if (this.auth.verifyCredentials()) { 
  18.       // ... 
  19.     } 
  20.   } 

开放闭合原则 (OCP)

软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。这个原则是诸多面向对象编程原则中最抽象、最难理解的一个。

说大白话就是:你不是要变化吗?,那么我就让你继承实现一个对象,用一个接口来抽象你的职责,你变化越多,继承实现的子类就越多。

「不好的写法」

  1. class AjaxAdapter extends Adapter { 
  2.   constructor() { 
  3.     super(); 
  4.     this.name = "ajaxAdapter"
  5.   } 
  6.  
  7. class NodeAdapter extends Adapter { 
  8.   constructor() { 
  9.     super(); 
  10.     this.name = "nodeAdapter"
  11.   } 
  12.  
  13. class HttpRequester { 
  14.   constructor(adapter) { 
  15.     this.adapter = adapter; 
  16.   } 
  17.  
  18.   fetch(url) { 
  19.     if (this.adapter.name === "ajaxAdapter") { 
  20.       return makeAjaxCall(url).then(response => { 
  21.         // transform response and return 
  22.       }); 
  23.     } else if (this.adapter.name === "nodeAdapter") { 
  24.       return makeHttpCall(url).then(response => { 
  25.         // transform response and return 
  26.       }); 
  27.     } 
  28.   } 
  29.  
  30. function makeAjaxCall(url) { 
  31.   // request and return promise 
  32.  
  33. function makeHttpCall(url) { 
  34.   // request and return promise 

「好的写法」

  1. class AjaxAdapter extends Adapter { 
  2.   constructor() { 
  3.     super(); 
  4.     this.name = "ajaxAdapter"
  5.   } 
  6.  
  7.   request(url) { 
  8.     // request and return promise 
  9.   } 
  10.  
  11. class NodeAdapter extends Adapter { 
  12.   constructor() { 
  13.     super(); 
  14.     this.name = "nodeAdapter"
  15.   } 
  16.  
  17.   request(url) { 
  18.     // request and return promise 
  19.   } 
  20.  
  21. class HttpRequester { 
  22.   constructor(adapter) { 
  23.     this.adapter = adapter; 
  24.   } 
  25.  
  26.   fetch(url) { 
  27.     return this.adapter.request(url).then(response => { 
  28.       // transform response and return 
  29.     }); 
  30.   } 

里氏替换原则(LSP)

里氏替换原则 :里氏替换原则 认为“程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的”的概念。

LSP则给了我们一个判断和设计类之间关系的基准:需不需 要继承,以及怎样设计继承关系。

当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。继承对于「OCP」,就相当于多态性对于里氏替换原则。子类可以代替基类,客户使用基类,他们不需要知道派生类所做的事情。这是一个针对行为职责可替代的原则,如果S是T的子类型,那么S对象就应该在不改变任何抽象属性情况下替换所有T对象。

客户模块不应关心服务模块的是如何工作的;同样的接口模块之间,可以在不知道服务模块代码的情况下,进行替换。即接口或父类出现的地方,实现接口的类或子类可以代入。

「不好的写法」

  1. class Rectangle { 
  2.   constructor() { 
  3.     this.width = 0
  4.     this.height = 0
  5.   } 
  6.  
  7.   setColor(color) { 
  8.     // ... 
  9.   } 
  10.  
  11.   render(area) { 
  12.     // ... 
  13.   } 
  14.  
  15.   setWidth(width) { 
  16.     this.width = width; 
  17.   } 
  18.  
  19.   setHeight(height) { 
  20.     this.height = height; 
  21.   } 
  22.  
  23.   getArea() { 
  24.     return this.width * this.height; 
  25.   } 
  26.  
  27. class Square extends Rectangle { 
  28.   setWidth(width) { 
  29.     this.width = width; 
  30.     this.height = width
  31.   } 
  32.  
  33.   setHeight(height) { 
  34.     this.width = height
  35.     this.height = height; 
  36.   } 
  37.  
  38. function renderLargeRectangles(rectangles) { 
  39.   rectangles.forEach(rectangle => { 
  40.     rectangle.setWidth(4); 
  41.     rectangle.setHeight(5); 
  42.     const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20. 
  43.     rectangle.render(area); 
  44.   }); 
  45.  
  46. const rectangles = [new Rectangle(), new Rectangle(), new Square()]; 
  47. renderLargeRectangles(rectangles); 

「好的写法」

  1. class Shape { 
  2.   setColor(color) { 
  3.     // ... 
  4.   } 
  5.  
  6.   render(area) { 
  7.     // ... 
  8.   } 
  9.  
  10. class Rectangle extends Shape { 
  11.   constructor(width, height) { 
  12.     super(); 
  13.     this.width = width; 
  14.     this.height = height; 
  15.   } 
  16.  
  17.   getArea() { 
  18.     return this.width * this.height; 
  19.   } 
  20.  
  21. class Square extends Shape { 
  22.   constructor(length) { 
  23.     super(); 
  24.     this.length = length; 
  25.   } 
  26.  
  27.   getArea() { 
  28.     return this.length * this.length; 
  29.   } 
  30.  
  31. function renderLargeShapes(shapes) { 
  32.   shapes.forEach(shape => { 
  33.     const area = shape.getArea(); 
  34.     shape.render(area); 
  35.   }); 
  36.  
  37. const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)]; 
  38. renderLargeShapes(shapes); 

接口隔离原则(ISP)

接口隔离原则 :接口隔离原则 认为“多个特定客户端接口要好于一个宽泛用途的接口”的概念。

不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。

这个原则起源于施乐公司,他们需要建立了一个新的打印机系统,可以执行诸如装订的印刷品一套,传真多种任务。此系统软件创建从底层开始编制,并实现了这些 任务功能,但是不断增长的软件功能却使软件本身越来越难适应变化和维护。每一次改变,即使是最小的变化,有人可能需要近一个小时的重新编译和重新部署。这 是几乎不可能再继续发展,所以他们聘请罗伯特Robert帮助他们。他们首先设计了一个主要类Job,几乎能够用于实现所有任务功能。只要调用Job类的 一个方法就可以实现一个功能,Job类就变动非常大,是一个胖模型啊,对于客户端如果只需要一个打印功能,但是其他无关打印的方法功能也和其耦合,ISP 原则建议在客户端和Job类之间增加一个接口层,对于不同功能有不同接口,比如打印功能就是Print接口,然后将大的Job类切分为继承不同接口的子 类,这样有一个Print Job类,等等。

「不好的写法」

  1. class DOMTraverser { 
  2.   constructor(settings) { 
  3.     this.settings = settings; 
  4.     this.setup(); 
  5.   } 
  6.  
  7.   setup() { 
  8.     thisthis.rootNode = this.settings.rootNode; 
  9.     this.animationModule.setup(); 
  10.   } 
  11.  
  12.   traverse() { 
  13.     // ... 
  14.   } 
  15.  
  16. const $ = new DOMTraverser({ 
  17.   rootNode: document.getElementsByTagName("body"), 
  18.   animationModule() {} // Most of the time, we won't need to animate when traversing. 
  19.   // ... 
  20. }); 

「好的写法」

  1. class DOMTraverser { 
  2.   constructor(settings) { 
  3.     this.settings = settings; 
  4.     this.options = settings.options; 
  5.     this.setup(); 
  6.   } 
  7.  
  8.   setup() { 
  9.     thisthis.rootNode = this.settings.rootNode; 
  10.     this.setupOptions(); 
  11.   } 
  12.  
  13.   setupOptions() { 
  14.     if (this.options.animationModule) { 
  15.       // ... 
  16.     } 
  17.   } 
  18.  
  19.   traverse() { 
  20.     // ... 
  21.   } 
  22.  
  23. const $ = new DOMTraverser({ 
  24.   rootNode: document.getElementsByTagName("body"), 
  25.   options: { 
  26.     animationModule() {} 
  27.   } 
  28. }); 

依赖倒置原则(DIP)

依赖倒置原则:依赖倒置原则 认为一个方法应该遵从“依赖于抽象而不是一个实例” 的概念。依赖注入是该原则的一种实现方式。

依赖倒置原则(Dependency Inversion Principle,DIP)规定:代码应当取决于抽象概念,而不是具体实现。

类可能依赖于其他类来执行其工作。但是,它们不应当依赖于该类的特定具体实现,而应当是它的抽象。这个原则实在是太重要了,社会的分工化,标准化都 是这个设计原则的体现。显然,这一概念会大大提高系统的灵活性。如果类只关心它们用于支持特定契约而不是特定类型的组件,就可以快速而轻松地修改这些低级 服务的功能,同时最大限度地降低对系统其余部分的影响。

「不好的写法」

  1. class InventoryRequester { 
  2.   constructor() { 
  3.     this.REQ_METHODS = ["HTTP"]; 
  4.   } 
  5.  
  6.   requestItem(item) { 
  7.     // ... 
  8.   } 
  9.  
  10. class InventoryTracker { 
  11.   constructor(items) { 
  12.     this.items = items; 
  13.  
  14.     // BAD: We have created a dependency on a specific request implementation. 
  15.     // We should just have requestItems depend on a request method: `request` 
  16.     this.requester = new InventoryRequester(); 
  17.   } 
  18.  
  19.   requestItems() { 
  20.     this.items.forEach(item => { 
  21.       this.requester.requestItem(item); 
  22.     }); 
  23.   } 
  24.  
  25. const inventoryTracker = new InventoryTracker(["apples", "bananas"]); 
  26. inventoryTracker.requestItems(); 

「好的写法」

  1. class InventoryTracker { 
  2.   constructor(items, requester) { 
  3.     this.items = items; 
  4.     this.requester = requester; 
  5.   } 
  6.  
  7.   requestItems() { 
  8.     this.items.forEach(item => { 
  9.       this.requester.requestItem(item); 
  10.     }); 
  11.   } 
  12.  
  13. class InventoryRequesterV1 { 
  14.   constructor() { 
  15.     this.REQ_METHODS = ["HTTP"]; 
  16.   } 
  17.  
  18.   requestItem(item) { 
  19.     // ... 
  20.   } 
  21.  
  22. class InventoryRequesterV2 { 
  23.   constructor() { 
  24.     this.REQ_METHODS = ["WS"]; 
  25.   } 
  26.  
  27.   requestItem(item) { 
  28.     // ... 
  29.   } 
  30. const inventoryTracker = new InventoryTracker( 
  31.   ["apples", "bananas"], 
  32.   new InventoryRequesterV2() 
  33. ); 
  34. inventoryTracker.requestItems(); 

 

来源:大迁世界内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯