这是理解
SOLID
原则,介绍什么是
开闭原则以及它为何可以在对已有的软件系统或者模块提供新功能时,避免没必要要的更改(重复劳动)。
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.软件实体(类、模块、函数等)都应当对扩展具备开放性,可是对于修改具备封闭性。前端
首先,咱们假设在代码中,咱们已经有了若干抽象层代码,好比类、模块、高阶函数,它们都仅作一件事(还记得单一职责原则吗?),而且都作的十分出色,因此咱们想让它们始终处于简洁、高内聚而且好用的状态。node
可是另外一方面,咱们仍是会面临改变,这些改变包含范围(译者注:应当是指抽象模块的职责范围)的改变,新功能的增长请求还有新的业务逻辑需求。webpack
因此对于上面咱们所拥有的抽象层代码,在长期想让它处于一成不变的状态是不现实的,你不可避免的会针对以上的须要做出改变的需求,增长更多的功能,增长更多的逻辑和交互。在上一篇文章,咱们知道,改变会使系统复杂,复杂会促使模块间的耦合性上升,因此咱们迫切地须要寻找一种方法可以使咱们的抽象模块不只能够扩大它的职责范围,同时还可以保持当前良好的状态(简洁、高内聚、好用)。git
这即是开闭原则存在的意义,它可以帮助咱们完美地实现这一切。github
当你须要对已有代码做出一些修改时,请切记如下两点:web
这里关于继承,咱们特地增长了一个注释,在这种状况下使用继承可能会使模块之间耦合在一块儿,同时这种耦合是可避免的,咱们一般在一些预先有着良好定义的结构上使用继承。(译者注:这里应该是指,对于咱们预先设计好的功能,推荐使用继承方式,对于后续新增的变动需求,推荐使用组合方式)express
举个例子(译者注:我对这里的例子作了一些修改,原文中并无详细的说明)编程
interface IRunner { run: () => void; } class Runner implements IRunner { run(): void { console.log("9.78s"); } } interface IJumper { jump: () => void; } class Jumper implements IJumper { jump(): void { console.log("8.95,"); } }
例子中,咱们首先声明了一个IRunner
接口,以后又声明了IJumper
,并分别实现了它们,而且实现类的职能都是单一的。redux
假如如今咱们须要提供一个既会跑又会跳的对象,若是咱们使用继承的方式,能够这么写后端
class RunnerAndJumper extends Runner { jump: () => void }
或者
class RunnerAndJumper extends Jumper { run: () => void }
可是使用继承的方式会使这个RunnerAndJumper
与Runner
(或者Jumper
)耦合在一块儿(耦合在一块儿的缘由是由于它会因它的父类改变而改变),咱们再来用组合的方式试试看,以下:
class RunnerAndJumper { private runnerClass: IRunner; private jumperClass: IJumper; constructor(runner: IRunner, jumper: IJumper) { this.runnerClass = new runner(); this.jumperClass = new jumper(); } run() { this.runnerClass.run(); } jump() { this.jumperClass.jump(); } }
咱们在RunnerAndJumper
的构造函数中声明两个依赖,一个是IRunner
类型,一个是IJumper
类型。
最终的代码其实和依赖倒置原则中的例子很像,并且你会发现,RunnerAndJumper
类自己并无与任何别的类耦合在一块儿,它的职能一样是单一的,它是对一个即会跑又会跳的实体的抽象,而且这里咱们还可使用DI(依赖注入)
技术进一步的优化咱们的代码,下降它的耦合度。
开闭原则所带来最有用的好处就是,当咱们在实现咱们的抽象层代码时,咱们就能够对将来可能须要做出改变的地方拥有一个比较完整的设想,这样当咱们真正面临改变时,咱们所对原有代码的修改,更贴近于改变自己,而不是一味的修改咱们已有的抽象代码。
在这种状况下,因为咱们节省了没必要要的劳动和时间,咱们就能够将更多的精力投入到关于更加长远的事宜计划上面,并且能够针对这些事宜须要做出的改变,提早和团队沟通,最终给予一套更加健壮、更符合系统模块自己的解决方案。
在整个软件开发周期中(好比一个敏捷开发周期),你对于整个周期中的事情了解的越透彻、越多,则越好。身为一个工程师,在一个开发冲刺中,为了在冲刺截止日期结束前,实现一个高效的、可靠的系统,你不会指望做出太多的改变,所以每每你可能会“偷工减料”。
从另外一个角度来说,咱们也应当致力于在每一次面临需求变动的状况下,不须要一而再,再而三的更改咱们已有的代码。全部新的功能都应当经过增长一个新的组合类或方法实现,或者经过复用已有的代码来实现。
充分贯彻开闭原则的另外一个例子,即是插件与中间件架构,咱们能够从三个角度来简单分析这种架构是如何运做的:
Chrome
。Redux
、express
还有不少框架都支持这样的功能。但愿这篇文章可以帮助你学会如何应用开闭原则而且从中收益。设计一个具备可组合性的系统,同时提供具备良好定义的扩展接口,是一种很是有用的技术,这种技术最关键的地方在于,它使咱们的系统可以在保持强健的同时,提供新功能、新特性,可是却不会影响它当前的状态。
开闭原则是面向对象编程中最重要的原则之一,有多重要呢?这么说吧,不少的设计原则和设计模式所但愿达成的最终状态,每每符合开闭原则,所以许多原则均可以做为实现开闭原则的一种手段,在原文的例子中,咱们能够很明显的体会到,在实现开闭原则所提倡的理念的过程当中,咱们不经意地使用以前两篇文章中涉及的原则,好比:
我以前一直是作后端相关工做的,因此对于开闭原则接触较早,这两年转行作了前端,随着nodejs
的发展,框架技术突飞猛进,可是其中脱颖而出的优秀框架每每是充分贯彻了开闭原则,好比express
、webpack
还有状态管理容器redux
,它们均是开闭原则的最佳实践。
另一方面,在这两年的工做也感觉到,适当的使用函数式编程的思想,每每是贯彻开闭原则一个比较好的开始,由于函数式的编程中的核心概念之一即是compose(组合)
。以函数式描述业务每每是原子级的指令,以后在须要描述更复杂的业务时,咱们复用并组合以前已经存在的指令以达到目的,这偏偏符合开闭原则所提倡的可组合性。
最后再分享一些前端工做中,常常须要使用开闭原则的最佳业务场景,
事件驱动模型:对于一些复杂的事件驱动模型,好比拖拽,每每使用开闭原则会达到意想不到的效果。最近有一个比较火的拖拽库draggable,提供的拖拽体验相比其余同类型的库简直不是一个级别。我前段时间去读它的源码,发现它之因此强大,是由于在它内部,针对多种拖拽事件,封装了独立的事件发射器(其内部称做Sensor
),以后根据这些发射器指定了一套独立的抽象事件驱动模型,在这个模型基础上,针对不一样的业务场景提供不一样的插件,好比:
能想到的大概就这么多,但愿能够抛砖引玉,若有错误,还望指正。
关注公众号 全栈101,只谈技术,不谈人生