391 lines
8.9 KiB
Markdown
391 lines
8.9 KiB
Markdown
---
|
||
nav:
|
||
title: 前端
|
||
path: /fea
|
||
group:
|
||
title: 💊 设计模式
|
||
order: 4
|
||
path: /pattern
|
||
---
|
||
|
||
## 结构型
|
||
|
||
结构型模式封装的是对象之间组合方式的变化,目的在于灵活地表达对象间的配合与依赖关系;
|
||
|
||
### 适配器模式
|
||
|
||
将一个类的接口转化为另外一个接口,以满足用户需求,使类之间接口不兼容问题通过适配器得以解决。
|
||
|
||
案例:新来的大卫封装了个 **fetch** 请求库,但是公司以前的网络请求方法是基于 **XMLHttpRequest** 的,老板想大卫去改这已经写好的 9999 个接口的请求,大卫使用了适配器模式去兼容,如下:
|
||
|
||
```js
|
||
// RequestUtil 请求库
|
||
export default class RequestUtil {
|
||
// get 方法
|
||
static get(url) {
|
||
return new Promise((resolve, reject) => {
|
||
fetch(url)
|
||
.then(response => response.json())
|
||
.then(result => {
|
||
resolve(result)
|
||
})
|
||
.catch(err => reject(err))
|
||
})
|
||
}
|
||
|
||
// post 方法
|
||
static post(url, data) {
|
||
return new Promise((resolve, reject) => {
|
||
fetch(url, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Accept': 'application/json'
|
||
},
|
||
body: data
|
||
})
|
||
.then(response => response.json())
|
||
.then(result => resolve(result))
|
||
.catch(err => reject(err))
|
||
})
|
||
}
|
||
}
|
||
|
||
// 调用
|
||
const postResp = await RequestUtils.post('https://nicecoders.github.io', data)
|
||
|
||
// 公司老的库
|
||
function Ajax(type, url, data, success, failed){
|
||
// 创建ajax对象
|
||
var xhr = null;
|
||
if(window.XMLHttpRequest){
|
||
xhr = new XMLHttpRequest();
|
||
} else {
|
||
xhr = new ActiveXObject('Microsoft.XMLHTTP')
|
||
}
|
||
|
||
...(此处省略一系列的业务逻辑细节)
|
||
|
||
var type = type.toUpperCase();
|
||
|
||
// 识别请求类型
|
||
if(type == 'GET'){
|
||
if(data){
|
||
xhr.open('GET', url + '?' + data, true); //如果有数据就拼接
|
||
}
|
||
// 发送get请求
|
||
xhr.send();
|
||
|
||
} else if(type == 'POST'){
|
||
xhr.open('POST', url, true);
|
||
// 如果需要像 html 表单那样 POST 数据,使用 setRequestHeader() 来添加 http 头。
|
||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||
// 发送post请求
|
||
xhr.send(data);
|
||
}
|
||
|
||
// 处理返回数据
|
||
xhr.onreadystatechange = function(){
|
||
if(xhr.readyState == 4){
|
||
if(xhr.status == 200){
|
||
success(xhr.responseText);
|
||
} else {
|
||
if(failed){
|
||
failed(xhr.status);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建适配器函数,入参与旧接口保持一致
|
||
async function AjaxAdapter(type, url, data, success, failed) {
|
||
const type = type.toUpperCase()
|
||
let result
|
||
try {
|
||
// 实际的请求全部由新接口发起
|
||
if(type === 'GET') {
|
||
result = await HttpUtils.get(url) || {}
|
||
} else if(type === 'POST') {
|
||
result = await HttpUtils.post(url, data) || {}
|
||
}
|
||
// 假设请求成功对应的状态码是1
|
||
result.statusCode === 1 && success ? success(result) : failed(result.statusCode)
|
||
} catch(error) {
|
||
// 捕捉网络错误
|
||
if(failed){
|
||
failed(error.statusCode);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 兼容了老的调用方式
|
||
Ajax('get', 'https://nicecoders.github.io', data, function(res){
|
||
// 成功的回调逻辑
|
||
}, function(error){
|
||
// 失败的回调逻辑
|
||
})
|
||
|
||
```
|
||
|
||
### 装饰器模式
|
||
|
||
- 动态地给某个对象添加一些额外的职责,,是一种实现继承的替代方案
|
||
- 在不改变原对象的基础上,通过对其进行包装扩展,使原有对象可以满足用户的更复杂需求,而不会影响从这个类中派生的其他对象
|
||
- 遵循拓展不修改的 SOLID 原则
|
||
|
||
#### 简单版
|
||
|
||
```jsx
|
||
import React, { useRef, useState, useEffect } from 'react';
|
||
import '@nicecode/css';
|
||
|
||
class Modal {
|
||
constructor(opt = {}) {
|
||
const { dom } = opt;
|
||
|
||
this.dom = dom;
|
||
}
|
||
|
||
show() {
|
||
this.dom.innerHTML = '卧槽';
|
||
this.dom.style.display = 'block';
|
||
this.dom.style.width = '200px';
|
||
this.dom.style.textAlign = 'center';
|
||
}
|
||
|
||
hide() {
|
||
this.dom.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
class DecoratorModal {
|
||
constructor(_oldModal) {
|
||
this._oldModal = _oldModal;
|
||
}
|
||
|
||
show() {
|
||
this._oldModal.show();
|
||
|
||
this._oldModal.dom.innerHTML = '添加背景+文字减淡+圆角';
|
||
this._oldModal.dom.style.color = '#aaa';
|
||
this._oldModal.dom.style.borderRadius = '5px';
|
||
}
|
||
|
||
hide() {
|
||
this._oldModal.hide();
|
||
}
|
||
}
|
||
|
||
export default () => {
|
||
const modalRef = useRef(null);
|
||
const [modal, setModal] = useState(null);
|
||
// 案例:原本有个按钮,新的需求要将按钮样式置灰,并且文案改为 快去登录
|
||
const openModal = () => {
|
||
modal.show();
|
||
};
|
||
|
||
const hideModal = () => {
|
||
modal.hide();
|
||
};
|
||
|
||
const decoratorModal = () => {
|
||
let dom = new DecoratorModal(modal);
|
||
|
||
setModal(dom);
|
||
};
|
||
|
||
const normalModal = () => {
|
||
let dom = new Modal({
|
||
dom: modalRef.current,
|
||
});
|
||
|
||
setModal(dom);
|
||
};
|
||
|
||
useEffect(() => {
|
||
normalModal();
|
||
}, []);
|
||
|
||
let style = {
|
||
margin: '0 6px',
|
||
};
|
||
|
||
return (
|
||
<div className="decorator">
|
||
<button style={style} onClick={openModal}>
|
||
打开弹框
|
||
</button>
|
||
<button style={style} onClick={hideModal}>
|
||
关闭弹框
|
||
</button>
|
||
<button style={style} onClick={decoratorModal}>
|
||
添加适配器
|
||
</button>
|
||
<button style={style} onClick={normalModal}>
|
||
清除适配器
|
||
</button>
|
||
<div
|
||
ref={modalRef}
|
||
style={{
|
||
display: 'none',
|
||
marginTop: '20px',
|
||
padding: '10px 20px',
|
||
border: '1px solid #eee',
|
||
}}
|
||
></div>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
#### 进阶版
|
||
|
||
```jsx
|
||
import React, { useRef, useEffect } from 'react';
|
||
|
||
function funcDecorator(type) {
|
||
return function (target, name, descriptor) {
|
||
if (type === 'class') {
|
||
target.prototype.show = () => {
|
||
console.log('装饰器处理后的类');
|
||
};
|
||
return target;
|
||
/**
|
||
* or
|
||
* return class NButton {
|
||
* show() {
|
||
* console.log('装饰器处理后')
|
||
* }
|
||
* }
|
||
**/
|
||
} else if (type === 'function') {
|
||
const old = descriptor.value;
|
||
descriptor.value = function (...arg) {
|
||
// 注意这里需要保留原this作用域,不能使用箭头函数
|
||
console.log('----装饰器装饰函数----');
|
||
// 原函数
|
||
return old.apply(this, arg);
|
||
};
|
||
}
|
||
};
|
||
}
|
||
|
||
// 通过装饰器改变原有的 show 方法
|
||
// @funcDecorator('class')
|
||
class Button {
|
||
show() {
|
||
console.log('大卫的思想空间');
|
||
}
|
||
|
||
@funcDecorator('function')
|
||
mb() {
|
||
console.log('我是sb');
|
||
}
|
||
}
|
||
|
||
export default () => {
|
||
useEffect(() => {
|
||
let dom = new Button();
|
||
// dom.show()
|
||
// dom.mb()
|
||
// console.log(dom)
|
||
}, []);
|
||
|
||
return <div>进阶案例:控制台查看输出结果</div>;
|
||
};
|
||
```
|
||
|
||
### 代理模式
|
||
|
||
是为一个对象提供一个代用品或占位符,以便控制对它的访问
|
||
|
||
#### 简单版
|
||
|
||
```jsx
|
||
import React, { useRef, useEffect } from 'react';
|
||
|
||
// 普通私密信息
|
||
const baseInfo = ['name', 'age', 'career'];
|
||
// 最私密信息
|
||
const privateInfo = ['avatar', 'phone'];
|
||
|
||
// 规定礼物的数据结构由type和value组成
|
||
const present = {
|
||
type: '巧克力',
|
||
value: 60,
|
||
};
|
||
|
||
// 相亲男方
|
||
const user = {
|
||
isValidated: true,
|
||
isVIP: false,
|
||
};
|
||
|
||
// 相亲女方
|
||
const girl = {
|
||
// 姓名
|
||
name: '小美',
|
||
// 自我介绍
|
||
aboutMe: '...',
|
||
// 年龄
|
||
age: 24,
|
||
// 职业
|
||
career: 'teacher',
|
||
// 假头像
|
||
fakeAvatar: 'xxxx',
|
||
// 真实头像
|
||
avatar: 'xxxx',
|
||
// 手机号
|
||
phone: 123456,
|
||
// 礼物数组
|
||
presents: [],
|
||
// 拒收50块以下的礼物
|
||
bottomValue: 50,
|
||
// 记录最近一次收到的礼物
|
||
lastPresent: present,
|
||
};
|
||
|
||
// 掘金婚介所推出了小礼物功能
|
||
const JuejinLovers = new Proxy(girl, {
|
||
get: function (girl, key) {
|
||
if (baseInfo.indexOf(key) !== -1 && !user.isValidated) {
|
||
alert('您还没有完成验证哦');
|
||
return;
|
||
}
|
||
|
||
// 此处我们认为只有验证过的用户才可以购买VIP
|
||
if (user.isValidated && privateInfo.indexOf(key) !== -1 && !user.isVIP) {
|
||
alert('只有VIP才可以查看该信息哦');
|
||
return;
|
||
}
|
||
|
||
return girl[key];
|
||
},
|
||
set: function (girl, key, val) {
|
||
// 最近一次送来的礼物会尝试赋值给lastPresent字段
|
||
// 需要返回 boolean 判断是否赋值成功
|
||
if (key === 'lastPresent') {
|
||
if (val.value < girl.bottomValue) {
|
||
alert('sorry,您的礼物被拒收了');
|
||
return false;
|
||
}
|
||
|
||
// 如果没有拒收,则赋值成功,同时并入presents数组
|
||
girl.lastPresent = val;
|
||
girl.presents = [...girl.presents, val];
|
||
return true;
|
||
}
|
||
},
|
||
});
|
||
|
||
export default () => {
|
||
useEffect(() => {
|
||
console.log(JuejinLovers.name);
|
||
JuejinLovers.lastPresent = present;
|
||
console.log(JuejinLovers);
|
||
}, []);
|
||
|
||
return <div>hi</div>;
|
||
};
|
||
```
|