设计原则
设计模式共有六大原则
- 单一职责原则:实现类要职责单一
- 里氏替换原则:不要破坏继承体系
- 依赖倒置原则:面向接口编程
- 接口隔离原则:设计接口时精简单一
- 最少知识原则:降低耦合
- 开闭原则:对拓展开放,对修改关闭
单一职责原则SRP
- 单一职责原则 (Single Responsibility Principle, SRP)是指对一个类(方法、对象,下文统称对象)来说,应该仅有一个引起它变化的原因。简单来讲,一个对象只做一件事。
- 作用:
- 令我们对对象的维护变得简单。
- 如果一个对象具有多个职责的话,那么一个职责的逻辑需要修改,那么势必会影响到其他职责的代码。这就是职责之间互相耦合,耦合越强,对模块的修改就越来越危险。
- 优点:
- 降低单个类(方法,对象)的复杂度,提高可读性和可维护性,功能之间的界限更清晰;类(方法,对象)之间根据功能被分为更小的粒度,有助于代码的复用。
- 缺点
- 增加系统中类(方法,对象)的个数,实际上也增加了这些对象之间相互联系的难度,引入了额外的复杂度。
开放封闭原则 OCP
- 是指一个模块在扩展性方面应该是开放的,而在更改性方面应该是封闭的,也就是对扩展开放,对修改封闭。
- 当需要增加需求时,尽量通过拓展新代码的方式,而不是修改已有代码。因为修改已有代码会给依赖原有代码的模块带来隐患,需要全部重新测试一遍,带来了巨大的成本,并且对于已经上线的大型项目来讲风险和成本更高。
- 作用
- 增加可维护性,避免因为修改给系统带来的不稳定性
最少知识原则LKP
- 最少知识原则 (Least Knowledge Principle, LKP)又称为迪米特原则 (Law of Demeter, LOD),一个对象应该对其他对象有最少的了解。
- 通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,类的内部如何实现、如何复杂都与调用者或者依赖者没关系,调用者或者依赖者只需要知道他需要的方法即可,其他的我一概不关心。类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
- 通常为了减少对象之间的联系,是通过引入一个第三者来帮助进行通信,阻隔对象之间的直接通信,从而减少耦合。
- 优点
- 降低类(方法,对象)之间不必要的依赖,减少耦合
- 缺点
- 需要权衡引入第三方带来的复杂度是否值得
创建型模式
单例模式
- 单例模式 (Singleton Pattern)又称为单体模式,保证一个类只有一个实例,并提供一个访问它的全局访问点。也就是说,第二次使用同一个类创建新对象的时候,应该得到与第一次创建的对象完全相同的对象。
- 例子
- 浏览器中的window和document全局变量,这两个对象都是单例,任何时候访问它们都是一样的对象。window表示包含DOM文档的窗口,document是窗口中载入的DOM文档,分别提供了个字相关的方法。
- ES6新增的Module模块特性,通过import/export导出模块中的变量也是单例的,简单来说,如果在某个地方改变了模块内部变量的值,别的地方再引用的这个值就是改变后的值。
- Vuex,Redux,MobX等维护的全局状态,vue-router,ract-router等维护的路由实例,在单页应用的单页面中都属于单例的应用
- 优点
- 单例模式在创建后在内存中只存在一个实例,节约了内存开支和实例化时的性能开支,特别是需要重复使用一个创建开销比较大的类时,比起实例不断地销毁和重新实例化,单例能节约更多资源,典型的就是数据库连接。
- 单例模式可以解决对资源的多重占用,比如写文件操作只有一个实例,可以避免同时间对一个文件的多个操作。
- 缺点
- 对拓展不友好,一般不容易拓展,因为单例模式一般自行实例化,没有接口
- 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外部实例化方法
- 使用场景
- 当一个类的实例化过程消耗资源过多,使用单例模式避免性能浪费
- 当项目中需要一个公共的状态,那么需要使用单例模式来保证访问一致性
工厂模式
- 工厂模式 (Factory Pattern),根据不同的输入返回不同类的实例,一般用来创建同一类对象。工厂方式的主要思想是将对象的创建与对象的实现分离。
- 特点
- 访问者只需要知道产品名就能从工厂获得对应实例
- 访问者不关心实例创建过程
- 例子
- document.createElement方法创建DOM元素:访问者只要提供标签名(如div,img),那么这个方法就会返回对应的DOM元素
- 工厂模式的本意是将实际创建对象的过程推迟到子类中,一般用抽象类的子类来具体实现。因为JavaScript中没有抽象类,所以我们可以简单的将工厂模式看作是一个实例化对象的工厂类即可。
- 优点
- 良好的封装,代码结构清晰,访问者无需知道对象的创建流程,特别是创建比较复杂的情况下
- 拓展性优良,通过工厂方法隔离了用户和创建流程隔离,复合开放封闭原则
- 解耦了高层逻辑和底层产品类,符合最少知识原则,不需要就不去交流
- 工厂模式的缺点:带来了额外的系统复杂度,增加了抽象性
- 使用场景
- 对象的创建比较复杂,而访问者无需知道创建的具体流程
- 处理大量具有相同属性的小对象
建造者模式
- 建造者模式(Builder Pattern)又称生成器模式,分步构建一个复杂对象,并允许按步骤构造。同样的构建过程可以采用不同的表示,将一个复杂对象的构建层与其表示层分离。
- 与工厂模式不同的是,建造者模式更注重对象的创建过程,因此我们将创建的复杂对象模块化,使被创建的对象每一个子模块都可以得到高质量的复用。
- 实战案例
// 汽车建造者
class CarBuilder {
constructor(engine, weight, height, color, tyre, name, type) {
this.engine = engine
this.weight = weight
this.height = height
this.color = color
this.tyre = tyre
this.name = name
this.type = type
}
setPropertyFuncChain() {
Object.getOwnPropertyNames(this)
.forEach(key => {
const funcName = 'set' + key.replace(/^\\w/g, str => str.toUpperCase())
this[funcName] = value => {
this[key] = value
return this
}
})
return this
}
}
const benchi = new CarBuilder().setPropertyFuncChain()
.setEngine('大马力发动机')
.setWeight('2ton')
.setHeight('2000mm')
.setColor('white')
.setTyre('大号轮胎')
.setName('奔驰')
.setType('AMG')
- 优点
- 表现分离:使产品的构建流程和产品的表现分离,也就是将产品的创建算法和产品组成的实现隔离,访问者不必知道产品部件的实现细节。
- 拓展方便:如果希望伸长一个装配顺序或方式不同的新产品,那么直接新建一个指挥者即可,不用修改既有代码,符合封闭开放的原则。
- 更好的复用性:建造者模式将产品的创建算法和产品组成的实现分离,所以产品创建的算法可以复用,产品部件的实现也可以复用,增加了灵活性。
- 缺点
- 不适用于产品之间差异性很大,复用性不高的情况
- 实例的创建增加了很多额外结构,增加了复杂度,如果对象粒度不高,那么最好直接创建对象。
- 适用场景
- 相同的方法,不同的执行顺序,生产不同产品
- 产品组成部件类似,通过不同组件获得不同产品时