这是我参与「第四届青训营 」笔记创作活动的第4天
设计模式———在软件开发过程中,针对特定问题简洁而优雅的解决方案!
背景:模式语言:城镇、建筑、建造;设计模式:可复用面向对象软件的基础。
本质:对类的封装性、继承性、多态性、关联关系、组合关系的面向对象设计原则的总结应用。
运用:浏览器,JavaScript,前端框架中很多地方都有设计模式的运用。
23种分类:创造型、结构型、行为型
创造型:简单工厂模式、工厂方法模式、抽象工厂模式、建造者模式、单例模式
结构型:适配者模式、桥模式、组合模式、装饰器模式、外观模式、享元模式、代理模式
行为型:职责链模式、命令模式、迭代器模式、中介者模式、备忘录模式、观察者模式、访问者模式、策略模式、状态模式、模板方法模式、解释器模式
原则:SOLID
- [S - Single-responsiblity Principle]单一职责原则
- [O - Open-closed Principle]开放闭合原则,如Redux、Koa、Draft.js 库、插件、框架等
- [L - Liskov Substitution Principle]里氏置换原则,继承复用的基础,派生类可替换基类、基类功能不受破坏,派生类可增加新的行为
- [I - Interface Segregation Principle]接口独立原则,减少耦合
- [D - Dependency Inversion Principle]依赖倒置原则,高层次模块不依赖低层次模块 其他原则:
- [Composite Reuse Principle]组合复用原则,模块相互独立
- [Demeter Principle]迪米特原则,如函数式编程
1. 单例模式
- 定义:一个类一个实例,并提供访问它的全局访问点
- 场景:dialog弹窗、Window、请求缓存
const getSingle = (fn) => {
let result;
return (...args) => {
return result || result = fn.apply(this, args);
}
}
let createDialogLayer = () => {
let div = document.createElement('div');
div.style.display = 'none';
document.body.appendChild('div');
return div;
}
let singleDialog = getSingle(createDialogLayer);
2. 策略模式
- 定义:定义一系列算法,把它们封装成策略类,算法被封装在策略内部的方法实现里,将算法的使用和算法的实现分离开来。
- 场景:绩效考核、表单验证
// 场景:根据超过10种不同的类型,有不同的详情数据,不同的表单UI进行验证,同时进行新增、编辑操作(以下例子只列举了五种,实际代码更长)
// index.vue 代码1
// 校验后新增、编辑
save() {
if (type === '1') {
let data = this.$refs[type].submit();
if (!data) return;
this.config1 = data;
} else if (type === '2') {
let data = this.$refs[type].submit();
if (!data) return;
this.config2 = data;
} else if (type === '3') {
let data = this.$refs[type].submit();
if (!data) return;
this.config3 = data;
} else if (type === '4') {
let data = this.$refs[type].submit();
if (!data) return;
this.config4 = data;
} else if (type === '5') {
...
}
if (action === 'add') {
if (type === '1') {
this.addData = { id, name, config: this.config1 };
} else if (type === '2') {
this.addData = { id, name, config: this.config2 };
} else if (type === '3') {
this.addData = { id, name, config: this.config3 };
} else if (type === '4') {
this.addData = { id, name, config: this.config4 };
} else if (type === '5') {
...
}
} else if (action === 'edit') {
if (type === '1') {
this.editData1 = { id, name, config: this.config1 };
} else if (type === '2') {
this.editData2 = { id, name, config: this.config2 };
} else if (type === '3') {
this.editData3 = { id, name, config: this.config3 };
} else if (type === '4') {
this.editData4 = { id, name, config: this.config4 };
} else if (type === '5') {
...
}
}
}
detail() {
// 接口返回数据res.config;
if (type === '1') {
this.detailData1 = res.config;
} else if (type === '2') {
this.detailData2 = res.config;
} else if (type === '3') {
this.detailData3 = res.config;
} else if (type === '4' {
this.detailData4 = res.config;
} else if (type === '5') {
...
}
}
// 使用策略模式
const strategies = {
[type]: {
handleAddData(formData) {},
handleEditData(formData) {},
handleDetailData(detailData) {},
handleValidateData(formData) {},
// 不同类型可以追加不同的方法
...
}
}
save({ type, formData }) {
const strategy = strategies[type];
this.config = strategy.handleValidateData(formData);
if (!this.config) return;
if (action === 'add') {
this.addData = this.handleAddData(formData);
} else {
this.editData = this.handleEditData(formData);
}
}
detail({ type, detailData }) {
const strategy = strategies[type];
this.detailData = this.handleDetailData(detailData);
}
3. 代理模式
- 定义:可自定义控制对原始对象的访问方式,允许监听属性更新前后做一些事情
- 场景:监控、代理工具、框架如ES6的代理模式 | Proxy
const proxy = new Proxy(target, handle);
const origin = {}
const obj = new Proxy(origin, {
get: function (target, propKey, receiver) {
return '10'
}
});
4. 发布订阅模式
- 定义:订阅对象发生变化通知订阅者
- 场景:DOM事件、消息通知、邮件订阅、上线订阅
type Notify = (user: User) => void
class User {
name: String;
status: 'offline' | 'online';
followers: { user: User; notify: Notify }[]
constructor(name: string) {
this.name = name;
this.status = 'offline';
this.followers = [];
}
subscribe(user: User; notify: Notify) {
user.followers.push({ user, notify });
}
online() {
this.status = 'online';
this.followers.forEach(({ notify }) => {
notify(this);
})
}
}
const user1 = new User('user1');
const user2 = new User('user2');
const notifyUser1 = () => {
console.log('user2 online');
}
user1.subscribe(user2, notifyUser1);
user2.online();
5. 命令模式
- 定义:执行某些特定事情的指令,解决请求发送者和接收着耦合问题
- 场景:IOT控制设备、游戏键盘按键执行动作
let gamePlayer = {
jump: function(){},
attack: function(){},
defense: function(){}
}
// 创建命令
let makeCommand = (receiver, state) => {
return () => receiver[state]
}
// 键盘码命令
const commands = {
119: jump,
115: attack,
97: defense
}
// 保存命令堆栈
let commandStack = [];
document.onkeypress = function(e) {
let command = makeCommand(gamePlayer, commands[e.keyCode]);
if (command) {
command();
commandStack.push(command);
}
}
document.getElementById('play').onclick = function() {
let command;
while(command = commandStack.shift()) {
command();
}
}
6. 装饰器模式
- 定义:动态增加职责,即用即付
- 场景:数据上报、插件式表单验证
// validate和formSubmit未分离
let formSubmit = function() {
if (!validate()) return;
ajax('http://xxx/login', { user, password });
}
// 改写
Function.prototype.before = function(beforeFn) {
let _self = this;
return function() {
if (!beforeFn.apply(this, arguments)) return;
return _self.apply(this, arguments);
}
let validate = function() {
return user && password;
}
let formSubmit = function() {
ajax('http://xxx/login', { user, password });
}
formSubmit = formSubmit.before(validate);
formSubmit();
}
7. 组合模式
// https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch12s01.html
// Single elements 单个独立使用
$( "#singleItem" ).addClass( "active" );
$( "#container" ).addClass( "active" );
// Collections of elements // 多个组合使用
$( "div" ).addClass( "active" );
$( ".item" ).addClass( "active" );
$( "input" ).addClass( "active" );
const jQuery = {
addClass: function( value ) {
var classNames, i, l, elem,
setClass, c, cl;
if ( jQuery.isFunction( value ) ) {
return this.each(function( j ) {
jQuery( this ).addClass( value.call(this, j, this.className) );
});
}
if ( value && typeof value === "string" ) {
classNames = value.split( rspace );
for ( i = 0, l = this.length; i < l; i++ ) {
elem = this[ i ];
if ( elem.nodeType === 1 ) {
if ( !elem.className && classNames.length === 1 ) {
elem.className = value;
} else {
setClass = " " + elem.className + " ";
for ( c = 0, cl = classNames.length; c < cl; c++ ) {
if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
setClass += classNames[ c ] + " ";
}
}
elem.className = jQuery.trim( setClass );
}
}
}
}
return this;
}
}
8. 适配器模式
- 定义:解决两个接口不兼容的问题,不需要改变现有的接口,就能够使他们协同作用
- 场景:接口不兼容问题、SDK
// 参考天气库适配案例
// [Adapter Pattern 介紹及 JavaScript 實作](https://medium.com/%E5%93%88%E5%98%8D-%E4%B8%96%E7%95%8C/adapter-pattern-%E4%BB%8B%E7%B4%B9%E5%8F%8A-javascript-%E5%AF%A6%E4%BD%9C-c4f168f1cd26)
参考链接
s-o-l-i-d-the-first-5-priciples-of-object-oriented-design-with-javascript
betterprogramming-javascript-design-patterns
codesource-javascript-design-patterns
hackernoon-solid-principles-in-javascript
dofactory-design-patterns
一些软件设计的原则
JavaScript-Module-Pattern-In-Depth