1. 什么是设计模式
“模式”一词来源于建筑学,用于指代不同建筑结构设计中的相似性。由此启发,《设计模式:可复用面向对象软件的基础》一书把“模式”的观点应用于面向对象的软件设计中,总结了23种常见的软件开发设计模式。
设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。
可以说,设计模式是在某种场合下对某个问题的一种解决方案。也可以说,设计模式就是给面向对象软件开发中的一些好的设计取个名字。这些设计或许由来已久,甚至已经被很多人使用,通过被赋予一个具有代表性的名字,而被更好的分享和传播。
2. 设计原则 SOLID
SOLID由罗伯特·C·马丁在21世纪早期引入,指代了面向对象编程和面向对象设计的五个基本原则,指出了编写可维护性、可扩展性程序的方向。
2.1 单一职责原则(Single Responsibility Principle)
职责,被定义为“引起变化的原因”。
如果有两个动机去改写一个方法,那么这个方法就具有两个职责。如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。这个方法通常是一个不稳定的方法,特别是当职责之间有耦合,一个职责的变化会引起其他职责的变化,修改代码是一件危险的事情。
就一个方法而言,应该仅有一个引起它变化的原因,或者说只做一件事情。
但并不是所有的职责都应该一一分离。比如职责若总是同时变化,分离会增大了它们之间相互联系的难度。什么时候应该分离职责,是运用SRP的难点。
2.2 开放封闭原则(Opened Closed Principle)
软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。
对扩展开放,对修改关闭:当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码。
常见的违反 OCP 的例子:
var Teacher = function () {};
var Student = function () {};
var getTask = function (person) {
if (person instanceof Teacher) {
console.log('teach');
} else if (person instanceof Student) {
console.log('study');
}
};
getTask(new Teacher()); // teach
getTask(new Student()); // study
每当我们需要增加一种人物角色的时候,都需要改动 getPrinciple 函数的内部实现:
var Programmer = function () {};
var getTask = function (person) {
if (person instanceof Teacher) {
console.log('teach');
} else if (person instanceof Student) {
console.log('study');
} else if (person instanceof Programmer) {
console.log('coding');
}
};
getTask(new Programmer()); // coding
这样的方法是很不稳定的。可见每个人物都有相应的任务,只是执行的任务各不相同。以下是利用多态的思想改写上例:
var getTask = function (person) {
person.task()
};
var Teacher = function () {};
Teacher.prototype.task = function () {
console.log('teach');
};
var Student = function () {};
Student.prototype.task = function () {
console.log('study');
};
getTask(new Teacher()); // teach
getTask(new Student()); // study
// ===== 增加人物
var Programmer = function () {};
Programmer.prototype.task = function () {
console.log('coding');
};
getTask(new Programmer()); // coding
将稳定不变的部分和容易变化的部分隔离,程序就具有了可扩展性,只需增加代码,而不改动原来的程序。
2.3 里式替换原则(Liskov Substitution Principle)
Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.
继承必须确保超类所拥有的性质在子类中仍然成立。
任何应用父类的地方,都可以使用其子类进行替换。
LSP 是对开闭原则的补充。子类可以扩展父类的功能,但不能改变父类原有的功能。
LSP 克服了继承中重写父类造成的可复用性变差的缺点,保证类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
2.4 接口隔离原则(Interface Segregation Principle)
Clients should not be forced to depend upon interfaces that they don’t use. (客户端不应该依赖它不需要的接口)
The dependency of one class to another one should depend on the smallest possible interface. (类间的依赖关系应该建立在最小的接口上)
ISP 要求尽量将庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户端需要的方法。即为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
ISP 强调降低依赖,降低耦合。
2.5 依赖倒置原则(Dependency Inversion Principle)
High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions.
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
DIP 的核心思想是:要面向接口编程,不要面向实现编程。
定义中的抽象指的是接口或是抽象类,而细节则指的是具体的实例。在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定得多。
使用抽象制定好规范,而不涉及任何具体的操作,把展现细节的任务交给它们的实例去完成。
3. 设计模式分类
从第二节设计原则可以看出,设计模式的核心思想是封装变化,将变与不变分离,确保变化的部分灵活、不变的部分稳定。
根据封装变化的不同,设计模式可以分为以下三种类型:
- 创建型:封装了创建对象过程中的变化,将对象的创建与使用分离。如单例模式、工厂模式。
- 结构型:封装了对象之间组合方式的变化,灵活地表达对象间的配合与依赖关系。如适配器模式、装饰器模式。
- 行为型:用于描述程序在运行时复杂的流程控制,将多变的行为进行抽离。如观察者模式、迭代器模式。
将23种设计模式进行划分:
后续文章会陆续分享在 JavaScript 开发中常见的一些设计模式。