单一原则
定义
单一职责原则(SRP)的职责被定义为“引起变化的原因”。
简单讲就是你会去改动代码的原因。一个改动的原因就是一个职责,两个就是两个职责,以此类推。
过多的职责会导致方法的不稳定,变动就越大,并且会伴随改动带来的风险。低内聚和脆弱的设计的结果是低耦合性。
所以,SRP原则为:一个方法一个职责。
适用
什么时候需要做职责分离?
- 当两个或者多个职责是同时变化的时候,不需要分离他们
- 当两个或者多个职责没有变化的征兆的时候,不需要分离他们
考虑方便性和稳定性。
优缺点
优点:是降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,
这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他
的职责。
缺点:最明显的是会增加编写代码的复杂度。当我们按照职责把对象
分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度。
应用
代理模式、迭代器模式、单例模式和装饰者模式。
最少知识原则
定义
最少知识原则(LKP)说的是一个软件实体应当尽可能少地与其他实体发生相互作用。
最少知识原则也叫迪米特法则(Law of Demeter,LoD)
最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不
必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对
象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三
者对象来转发这些请求。
应用
中介者模式和外观模式
外观模式
观模式主要是为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个
接口使子系统更加容易使用
外观模式的作用是对客户屏蔽一组子系统的复杂性。外观模式对客户提供一个简单易用的高
层接口,高层接口会把客户的请求转发给子系统来完成具体的功能实现
如果外观不能满足客户的个性化需求,那么客户也可以选择越过外观来直接访问子系统。
封装
封装在很大程度上表达的是数据的隐藏。一个模块或者对象可以将内部的数据或者实现细
节隐藏起来,只暴露必要的接口 API 供外界访问。对象之间难免产生联系,当一个对象必须引
用另外一个对象的时候,我们可以让对象只暴露必要的接口,让对象之间的联系限制在最小的
范围之内。
同时,封装也用来限制变量的作用域。
开放-封闭原则
定义
软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。
应用
装饰者模式,策略模式,命令模式,发布-订阅模式,模板方法模式,代理模式,职责链模式。
开放-封闭原则的思想:当需要改变一个程序的功能或者给这个程序增加新功
能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码。
开放封闭原则是编写一个好程序的目标,其他设计原则都是达到这个目标的过程。
实现方式
- 对象的多态
- 放置挂钩
- 使用回调函数
多态
用对象的多态性消除条件分支
过多的条件分支语句是造成程序违反开放封闭原则的一个常见原因。每当需要增加一个新
的 if 语句时,都要被迫改动原函数。把 if 换成 switch-case 是没有用的,这是一种换汤不换药
的做法。实际上,每当我们看到一大片的 if 或者 switch-case 语句时,第一时间就应该考虑,能
否利用对象的多态性来重构它们。
回调函数
回调函数是一种特殊的挂钩。我们可以把一部分易于变化的逻辑封装在回调函数里,然后把
回调函数当作参数传入一个稳定和封闭的函数中。当回调函数被执行的时候,程序就可以因为回
调函数的内部逻辑不同,而产生不同的结果。
- 挑选出最容易发生变化的地方,然后构造抽象来封闭这些变化。
- 在不可避免发生修改的时候,尽量修改那些相对容易修改的地方。
接受第一次愚弄
有句古老的谚语说:“愚弄我一次,应该羞愧的是你。再次愚弄我,应该羞愧的是我”
让程序一开始就尽量遵守开放封闭原则,并不是一件很容易的事情。
一方面,我们需要尽快知道程序在哪些地方会发生变化,这要求我们有一些“未卜先知”的能力。
另一方面,留给程序员的需求排期并不是无限的,所以我们可以说服自己去接受不合理的代码带来的第一次愚弄。
在最初编写代码的时候,先假设变化永远不会发生,这有利于我们迅速完成需求。
当变化发生并且对我们接下来的工作造成影响的时候,可以再回过头来封装这些变化的地方。
然后确保我们不会掉进同一个坑里。
接口和面向接口编程
定义
接口是对象能响应的请求的集合。
抽象类
只要有可能,不要从具体类继承。
抽象类有以下两个作用。
- 向上转型。让 Duck 对象和 Chicken 对象的类型都隐藏在 Animal 类型身后,隐藏对象的具体
类型之后,duck 对象和 chicken 对象才能被交换使用,这是让对象表现出多态性的必经之路。 - 建立一些契约。继承自抽象类的具体类都会继承抽象类里的 abstract 方法,并且要求覆
写它们。这些契约在实际编程中非常重要,可以帮助我们编写可靠性更高的代码。比如
在命令模式中,各个子命令类都必须实现 execute 方法,才能保证在调用 command.execute
的时候不会抛出异常
总而言之,不关注对象的具体类型,而仅仅针对超类型中的“契约方法”来编写程序,可以
产生可靠性高的程序,也可以极大地减少子系统实现之间的相互依赖关系
接口
抽象类和 interface 的作用主要都是以下两点。
- 通过向上转型来隐藏对象的真正类型,以表现对象的多态性。
- 约定类与类之间的一些契约行为。
作用
接口在 JavaScript 中的最大作用退化到了检查代码的规范性。
- 检查某个对象是否实现了某个方法
- 检查是否给函数传入了预期类型的参数
如果没有这一层,我们需要增加防御性代码去判断方法和参数
接口检查很重要。
应用
TypeScript提供接口和参数验证等功能。
重构
提炼函数
- 避免出现超大函数。
- 独立出来的函数有助于代码复用。
- 独立出来的函数更容易被覆写。
- 独立出来的函数如果拥有一个良好的命名,它本身就起到了注释的作用。
合并重复的条件片段
如果一个函数体内有一些条件分支语句,而这些条件分支语句内部散布了一些重复的代码,
那么就有必要进行合并去重工作
把条件分支语句提炼成函数
把条件语句判断部分提取为函数,以函数返回来判断,语义化条件。
合理使用循环
提取公共元素组成类数组类型
提前让函数退出代替嵌套条件分支
将判断提前,不符合的先拒绝。
传递对象参数代替过长的参数列表
如题
尽量减少参数数量
能推断出的参数可能不比传入
少用三目运算符
- 如果条件分支逻辑简单且清晰,我们使用三目运算符
- 如果条件分支逻辑非常复杂,我们使用条件语句
- 一是阅读相对容易
- 二是修改的时候比修改三目运算符周围的代码更加方便
合理使用链式调用
在 JavaScript 中,可以很容易地实现方法的链式调用,即让方法调用结束后返回对象自身
如果该链条很容易发生变化,导致调试和维护困难
- 阅读难
- 调试难
使用条件: 是否结构稳定,后期不发生修改。
分解大型类
当context会变的庞大的时候,需要将里面的元素分离出来。
用 return 退出多重循环
在循环中退出后需要执行的逻辑封装成函数,最后是return 某个fn。