五大设计原则(SOLID)包括:
1. 单一职责原则(S):一个程序只做好一件事。功能独立拆开,保持每个部分干净独立,便于组合与复用。
2. 开放封闭原则(O):对扩展开放,对修改封闭。增加需求时,扩展新代码,而非修改已有代码。
3. 李氏置灰原则(L):子类能够覆盖父类。父类能够出现的地方,子类一样能出现。在JavaScript中使用较少(弱类型 & 继承使用较少)。
4. 接口独立原则(I):保持接口的单一独立,避免出现“胖接口”。类似于单一职责原则,这里更关注接口。在JavaScript中没有接口(typescript)除外,使用较少。
5. 依赖倒置原则(D):面向接口编程,依赖于抽象而不依赖于具体。使用方只关注于接口而不关注具体实现。在JavaScript中使用较少(没有接口 & 弱类型)。
此外,还有23种设计模式,分为创建型、结构型和行为型。其中创建型包括工厂模式、单例模式、原型模式;结构型包括适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式;行为型包括策略模式、模板方法模式、观察者模式、迭代器模式、职责链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式和解释器模式。
```javascript### 1. 构造函数和创建者分离
我们可以将类的实例化过程放在一个单独的地方,通过一个静态方法来实现。这样可以使得代码更加清晰易懂,也符合单一职责原则。
```javascript
class SingleObject {
constructor() {}
static showCart() {
console.log('显示购物车');
}
}
const singleObject = new SingleObject(); // 不推荐这种写法,因为singleObject在后续被赋值了
// const singleObject = SingleObject.getInstance(); // 应该使用getInstance方法获取实例
console.log(singleObject === SingleObject.instance); // true
```
### 2. 符合开放封闭原则
虽然这个示例中没有严格遵循开放封闭原则,但是我们可以通过定义一个静态方法来实现对实例的访问,从而达到开放封闭的目的。
### 3. 单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。这样可以避免重复创建对象,节省资源。
```javascript
class Singleton {
constructor() {}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new this();
}
return Singleton.instance;
}
}
const instance1 = Singleton.getInstance(); // 获取唯一实例
const instance2 = Singleton.getInstance(); // 还是获取到唯一实例
console.log(instance1 === instance2); // true
```
### 4. JavaScript中的demo代码:SingleObject和适配器模式
```javascript
// 原代码jquery的ajax请求,想要去除jquery库,这样就导致$.ajax 不能被使用者使用,需要适配兼容。 // 新ajax请求 ajax(someoptions) // 原项目中的jquery的ajax请求,当去除jquery后,需要保证原来的ajax请求功能可用 $.ajax() // 兼容适配: const $ = { ajax: function(options) { return ajax(options); //兼容支持 } };
```
```javascript// 设计原则验证:将现有对象与装饰器进行分离,两者独立存在
// 符合开放封闭原则
// 代理模式
// 使用者无法直接使用目标对象
// 通过中间加代理来授权和控制目标对象
// 例如:事件代理、
class Circle {
constructor() {}
draw() {
console.log('这是一个圆形');
}
}
class Decorator {
constructor(circle) {
this.circle = circle;
}
draw() {
console.log('--------------------');
this.circle.draw();
this.setRedBorder();
}
setRedBorder() {
console.log('我是添加的红色边框');
}
}
const circle = new Circle();
circle.draw();
const deCircle = new Decorator(circle);
deCircle.draw();
```
## 设计原则验证
在编写代码时,我们需要注意遵循一些设计原则。以下是我们对提供的代码进行验证后的一些发现:
1. 代理类和目标类分离,隔离开目标类和使用者。
2. 符合开放封闭原则。
3. 外观模式。
4. 谨慎使用,不滥用。
### 外观模式
外观模式是一个胖接口。它为子系统一组接口提供一个高层接口,使用者使用这个高层接口。这样可以使子系统中的接口更简洁,便于使用者理解和使用。
### demo代码
下面是使用外观模式的示例代码:
```javascript
function bindEvent(ele, type, selector, fn) {
if (fn == null) {
fn = selector;
selector = null;
}
// ....
}
// 即可以传四个参数、也可以传三个参数
bindEvent(eleDom, 'click', '#selector', fn);
bindEvent(eleDom, 'click', fn);
```
### 设计原则验证:不符合单一职责原则、开放封闭原则
在提供的代码中,我们可以看到以下不符合设计原则的地方:
1. 代理类和目标类分离不够明确,导致目标类和使用者之间的耦合度较高。这违反了单一职责原则。
2. 代码结构较为复杂,不利于扩展和维护。这违反了开放封闭原则。
```javascriptclass Subject {
constructor() {
this.state = null;
this.observes = [];
}
setState(state) {
this.state = state;
this.notifyAllObservers();
}
getState() {
return this.state;
}
attach(observer) {
this.observes.push(observer);
}
notifyAllObservers() {
this.observes.forEach((observer) => {
observer.update();
});
}
}
class Observer {
constructor(name, subject) {
this.name = name;
this.subject = subject;
this.subject.attach(this); //初始化观察者把自己添加到主题
}
update() {
console.log(`${this.name} update, state: ${this.subject.getState()}`);
}
}
//实例
const s = new Subject();
const ob1 = new Observer("m1", s);
const ob2 = new Observer("m2", s);
const ob3 = new Observer("m3", s);
s.setState(1);
s.setState(2);
s.setState(3);
```
以下是重构后的代码:
```javascript
// 迭代器类
class Iterator {
constructor (container) {
this.list = container.list;
this.index = 0;
}
// 获取下一个元素的值,如果没有则返回 null
next() {
if (this.hasNext()) {
return this.list[this.index++];
}
return null;
}
// 判断是否还有下一个元素
hasNext() {
if (this.index >= this.list.length) {
return false;
}
return true;
}
}
// Container类,用于存储列表并生成迭代器
class Container {
constructor (list) {
this.list = list;
}
// 根据当前Container实例生成迭代器工厂方法
createIterator() {
return new Iterator(this); // 将当前Container实例作为参数传递给Iterator构造函数。
};
}
// demo测试代码,创建一个包含1~6的list1,并使用createIterator生成迭代器进行遍历输出结果到控制台。(注意:由于JavaScript是弱类型语言,需要在调用next和hasNext方法时将变量声明为迭代器的原型属性,否则会出现语法错误)
const list1 = [1,2,3,4,5,6];
const container = new Container(list1); const iterator = container.createIterator(); //获得遍历器 while (iterator.hasNext()) { console.log(iterator.next()); //console输出迭代器当前指向的元素值。};
```javascript/**
* 实现迭代器模式
*/
function each(data) {
// 生成遍历器
let iterator = data[Symbol.iterator]()
console.log(iterator.next()) // {value: 1, done: false}
console.log(iterator.next()) // {value: 2, done: false}
console.log(iterator.next()) // {value: 3, done: false}
console.log(iterator.next()) // {value: 4, done: false}
console.log(iterator.next()) // {value: 5, done: false}
console.log(iterator.next()) // {value: 6, done: false}
console.log(iterator.next()) // {value: 7, done: false}
console.log(iterator.next()) // {value: 8, done: false}
console.log(iterator.next()) // {value: undefined, done: true}
}
let arr = [1,2,3,4,5,6,7,8]
each(arr)
```
```javascriptclass State {
constructor(color) {
this.color = color;
}
handle(context) {
console.log(`turn to ${this.color} light`);
context.setState(this);
// ... 其他操作逻辑
}
}
// 主体类
class Context {
constructor() {
this.state = null;
}
getState() {
return this.state;
}
setState(state) {
this.state = state;
}
}
const context = new Context();
const state1 = new State('red');
const state2 = new State('blue');
const state3 = new State('blank');
state1.handle(context); // 切换为 red
state2.handle(context); // 切换为 blue
state3.handle(context); // 切换为 blank
```
```javascript// 原型模式
const prototype = {
getInfo: function() {
return `name: ${this.name}, age: ${this.age}`;
},
sayInfo: function() {
console.log(this.getInfo());
}
};
const obj1 = Object.create(prototype);
obj1.name = 'meng';
obj1.age = 32;
obj1.sayInfo(); // name: meng, age: 32
// 桥接模式
class Color {
constructor (name) {
this.name = name;
}
}
class Shape {
constructor (name, color) {
this.name = name;
this.color = color;
}
draw() {
console.log(`颜色:${this.color.name}, 形状:${this.name}`);
}
}
const red = new Color('red');
const circle = new Shape('circle', red);
const yellow = new Color('yellow');
const triangle = new Shape('triangle', yellow);
circle.draw();
triangle.draw();
// 设计原则验证:抽象和实现分离,解耦,符合开放封闭原则。组合模式生成树形结构,表示“整体-部分”的关系,让整体和部分都具有一致的操作性。例如:vnode属性结构。demo代码。
```
```javascript// 定义一个 VNode 类,用于表示虚拟节点
class VNode {
constructor(tag, attr, children) {
this.tag = tag;
this.attr = attr;
this.children = children;
}
}
// 创建一个 vnode 实例
const vnode = new VNode(
'div',
[
{
tag: 'div',
attr: { id: 'item1', className: 'item' },
children: ['text node'],
},
{
tag: 'div',
attr: { id: 'item1', className: 'item' },
children: ['text node'],
},
{
tag: 'div',
attr: { id: 'item1', className: 'item' },
children: ['text node'],
},
],
);
```
```javascriptclass User {
constructor(type) {
this.type = type;
}
buy() {
if (this.type === 'ordinary') {
console.log('ordinary buy');
} else if (this.type === 'member') {
console.log('member buy');
} else if (this.type === 'vip') {
console.log('vip buy');
}
}
}
const ordinaryUser = new User('ordinary');
const memberUser = new User('member');
const vipUser = new User('vip');
ordinaryUser.buy();
memberUser.buy();
vipUser.buy();
console.log('改造后=====================================');
// 改造后模板方法模式
class UserTemplate {
constructor(type) {
this.type = type;
}
buy() {
this.beforeBuy();
this.doBuy();
this.afterBuy();
}
beforeBuy() {} // 在执行buy方法前需要执行的逻辑,可以为空函数
doBuy() {} // buy方法的具体实现,根据不同类型输出不同的提示信息
afterBuy() {} // 在执行buy方法后需要执行的逻辑,可以为空函数
}
const ordinaryUser = new UserTemplate('ordinary');
const memberUser = new UserTemplate('member');
const vipUser = new UserTemplate('vip');
ordinaryUser.buy();
memberUser.buy();
vipUser.buy();
```
## 职责链模式
在职责链模式中,每个处理步骤都封装在一个类中,并且这些类可以相互调用。这种模式使得系统的各个组成部分可以独立地改变自己的行为,而不需要修改其他部分的代码。
以下是使用 JavaScript 编写的示例代码:
```javascript
// 定义一个 Action 类,用于处理请求
class Action {
constructor(name) {
this.name = name;
this.nextAction = null;
}
// 设置下一个处理步骤
setNextAction(action) {
this.nextAction = action;
}
// 处理请求
handle() {
console.log(`${this.name} 操作`);
if (this.nextAction !== null) {
this.nextAction.handle();
}
}
}
// 创建 Action 实例并执行处理流程
const action = new Action('组长');
const jlAction = new Action('经理');
const zjAction = new Action('总监');
action.setNextAction(jlAction);
jlAction.setNextAction(zjAction);
action.handle();
```
### 命令模式
在命令模式中,将命令的执行者和请求者进行了分离。命令被封装成一个对象,通过调用对象的方法来执行相应的操作。请求者只需要与命令对象进行交互,而不需要关心命令的具体实现。这样就实现了请求者和执行者之间的解耦。
以下是使用 JavaScript 编写的示例代码:
```javascript
// 定义一个 Command 类,用于表示命令
class Command {
constructor() {}
execute() {}
}
// 定义一个 ConcreteCommand1 类,继承自 Command,并实现具体的命令逻辑
class ConcreteCommand1 extends Command {
constructor() {
super();
}
execute() {
console.log('执行具体命令1');
}
}
// 定义一个 ConcreteCommand2 类,继承自 Command,并实现具体的命令逻辑
class ConcreteCommand2 extends Command {
constructor() {
super();
}
execute() {
console.log('执行具体命令2');
}
}
// 将命令注册到一个命令处理器中,以便在需要时可以执行相应的命令
const commandProcessor = [];
commandProcessor.push(new ConcreteCommand1());
commandProcessor.push(new ConcreteCommand2());
commandProcessor[0].execute(); // 将输出 "执行具体命令1"
```javascript// 接受者类
class Receiver {
// 执行方法
exec() {
console.log('执行');
}
}
// 命令者类
class Command {
constructor(receiver) {
this.receiver = receiver;
}
// 命令方法
cmd() {
console.log('执行命令/号令');
this.receiver.exec();
}
}
// 发布者/触发者类
class Invoker {
constructor(command) {
this.command = command;
}
// 调用方法
invoke() {
console.log('发布/触发');
this.command.cmd();
}
}
// 创建接收者实例
const receiver = new Receiver();
// 将接收者实例传递给命令者实例
const command = new Command(receiver);
// 将命令者实例传递给发布者/触发者实例
const invoker = new Invoker(command);
// 调用发布者/触发者的方法,执行命令/号令并输出“执行”信息
invoker.invoke();
```
以下是重构后的代码:
```javascript
// 状态备忘类
class Momento {
constructor(content) {
this.content = content;
}
getContent() {
return this.content;
}
}
// 备忘录列表类
class CareTaker {
constructor() {
this.list = [];
}
// 添加备忘对象
add(momento) {
this.list.push(momento);
}
// 获取备忘对象
get(index) {
return this.list[index];
}
}
// 编辑器/操作类
class Editor {
constructor() {
this.content = null;
}
setContent(content) {
this.content = content;
}
getContent() {
return this.content;
}
saveContentToMomento() {
return new Momento(this.content);
}
getContentFromMomento(momento) {
this.content = momento.getContent();
}
}
const editor = new Editor();
const careTaker = new CareTaker();
editor.setContent('aaa'); // 将当前内容存入备忘列表并保存到第0个备忘对象中
careTaker.add(editor.saveContentToMomento()); // 将当前内容备忘列表并保存到第1个备忘对象中
editor.setContent('bbb'); // 将当前内容存入备忘列表并保存到第0个备忘对象中
careTaker.add(editor.saveContentToMomento()); // 将当前内容备忘列表并保存到第2个备忘对象中
editor.getContentFromMomento(careTaker.get(0)); // 从备忘列表获取第0个备忘对象并恢复为当前内容,此时内容为'aaa'。打印出当前内容,应为'aaa'。然后将当前内容从第0个备忘对象中取出,此时内容为'bbb',并恢复为当前内容,打印出当前内容,应为'bbb'。最后将当前内容从第1个备忘对象中取出,此时内容为'ccc',并恢复为当前内容,打印出当前内容,应为'ccc'。
设计原则验证:
状态对象与使用者分开,解耦:在上述代码中,我们可以看到A和B类的状态对象number是相互独立的,它们之间通过中介者模式进行交互。这种设计原则符合开放封闭原则,即对扩展开放,对修改封闭。
中介者模式:通过关联对象,通过中介者隔离。在上述代码中,Meditor类作为中介者,将A和B类的状态对象number进行关联,并通过中介者隔离的方式进行操作。这种设计原则同样符合开放封闭原则。
demo代码:
```javascript
class A { constructor () { this.number = 10 } setNumber (num, meditor) { this.number = num if (meditor) { meditor.setBNumber() } } }
class B { constructor () { this.number = 20 } setNumber (num, meditor) { this.number = num if (meditor) { meditor.setANumber() } } }
class Meditor { constructor (a, b) { this.a = a this.b = b } setANumber () { let number = this.a.number this.b.setNumber(number * 10) } setBNumber () { let number = this.b.number this.a.setNumber(number/10) } }
const a = new A()
const b = new B()
const meditor = new Meditor(a, b)
a.setNumber(10, meditor)
b.setNumber(20, meditor)
console.log(a.number, b.number)
```
访问者模式:将数据操作与数据结构进行分离。访问者模式主要用于对不同类型的数据结构进行操作,例如在处理JSON数据时,可以使用访问者模式来遍历和修改数据结构。然而,在上述代码中并没有使用访问者模式,因此这个设计原则在这里并不适用。
解释器模式:描述语言语法如何定义,如何解释和编译。解释器模式主要用于将一种语言的表示形式转换为另一种语言的表示形式。在实际应用中,解释器模式通常用于将一种编程语言转换为另一种编程语言,或者将一种脚本语言转换为另一种脚本语言。然而,在上述代码中并没有使用解释器模式,因此这个设计原则在这里并不适用。