nicenote/docs/fea/pattern/structure.md
2021-09-30 02:02:15 +08:00

8.7 KiB
Raw Blame History

nav group
title path
前端 /fea
title order
💊 设计模式 4

结构型

结构型模式封装的是对象之间组合方式的变化,目的在于灵活地表达对象间的配合与依赖关系;

适配器模式

将一个类的接口转化为另外一个接口,以满足用户需求,使类之间接口不兼容问题通过适配器得以解决。

案例:新来的大卫封装了个 fetch 请求库,但是公司以前的网络请求方法是基于 XMLHttpRequest老板想大卫去改这已经写好的9999个接口的请求大卫使用了适配器模式去兼容如下

// 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 原则

简单版

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>
  )
};

进阶版

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>
  )
}

代理模式

是为一个对象提供一个代用品或占位符,以便控制对它的访问

简单版

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>
  )
}