美文网首页
[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法)

[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法)

作者: woow_wu7 | 来源:发表于2021-10-12 08:49 被阅读0次
    design_principle设计原则.png Error.png

    导航

    [react] Hooks

    [封装-设计模式01] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式

    [React 从零实践01-后台] 代码分割
    [React 从零实践02-后台] 权限控制
    [React 从零实践03-后台] 自定义hooks
    [React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
    [React 从零实践05-后台] Gitlab-CI使用Docker自动化部署

    [源码-webpack01-前置知识] AST抽象语法树
    [源码-webpack02-前置知识] Tapable
    [源码-webpack03] 手写webpack - compiler简单编译流程
    [源码] Redux React-Redux01
    [源码] axios
    [源码] vuex
    [源码-vue01] data响应式 和 初始化渲染
    [源码-vue02] computed 响应式 - 初始化,访问,更新过程
    [源码-vue03] watch 侦听属性 - 初始化和更新
    [源码-vue04] Vue.set 和 vm.$set
    [源码-vue05] Vue.extend

    [源码-vue06] Vue.nextTick 和 vm.$nextTick
    [部署01] Nginx
    [部署02] Docker 部署vue项目
    [部署03] gitlab-CI

    [数据结构和算法01] 二分查找和排序

    [深入01] 执行上下文
    [深入02] 原型链
    [深入03] 继承
    [深入04] 事件循环
    [深入05] 柯里化 偏函数 函数记忆
    [深入06] 隐式转换 和 运算符
    [深入07] 浏览器缓存机制(http缓存机制)
    [深入08] 前端安全
    [深入09] 深浅拷贝
    [深入10] Debounce Throttle
    [深入11] 前端路由
    [深入12] 前端模块化
    [深入13] 观察者模式 发布订阅模式 双向数据绑定
    [深入14] canvas
    [深入15] webSocket
    [深入16] webpack
    [深入17] http 和 https
    [深入18] CSS-interview
    [深入19] 手写Promise
    [深入20] 手写函数
    [深入21] 数据结构和算法 - 二分查找和排序
    [深入22] js和v8垃圾回收机制
    [深入23] JS设计模式 - 代理,策略,单例

    [前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
    [前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
    [前端学java03-SpringBoot实战] lombok,日志,部署
    [前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
    [前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
    [前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
    [前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson

    (一) 前置知识

    (1) 一些单词

    principle 原则 原理
    responsibility 职责 责任
    substitution 代替 置换
    // Single Responsibility Principle 单一职责原则
    
    abstract 抽象的
    simple 简单的
    show up 出现,露面
    
    advanced 高级的,先进的
    configuration 配置
    // advanced configuration 高级配置
    
    constraint 约束
    peek 偷看
    shape 形状
    anonymous 匿名的
    

    (2) Error

    • new Error(message)
    • 抛出Error实例对象后,整个程序就中断在错误发生的地方,不再往下执行
    • error实例属性
      • message 错误提示信息
      • name 错误名称 ( 非标准属性 )
      • stack 错误堆栈 ( 非标准属性 )
    • 六种错误对象
      • SyntaxError 语法错误
      • ReferenceError ( 引用一个不存在的变量时发生的错误 ) 或者 ( 将一个值分配给无法分配的对象 )
      • RangeError 一个值超出有效范围时发生的错误
      • TypeError ( 变量或者参数 ) 不是 ( 预期类型 ) 时候发生的错误
      • RUIError URI 相关函数的参数不正确时抛出的错误
      • EvalError eval函数没有被正确执行时抛出的错误
    • 第七种,是自定义错误对象
      • 原理
        • 1.就是普通的构造函数(具有name,message,stack等属性)
        • 2.将构造函数的prototype属性指向 new Error() 生成的实例
        • 3.将构造函数的prototype.contructor属性指向构造函数自己,防止引用出错
    • throw语句
      • throw语句的作用是:手动中断程序执行,并抛出错误,即手动抛出错误
      • 注意:throw可以抛出任意类型的值,不一定是错误实例
      try {
            // throw new Error('错误了')
            // ---------------------------- 注意:throw 可以抛出任何类型的值,不局限于错误对象实例
            throw '错误了2'
      } catch(e) {
            // console.log(e.message) ----- 注意:这里对应的是try抛出的错误对象,错误对象具有message属性
            // ---------------------------- 注意:catch() 的参数是try代码块中抛出的值,即也可以是任意类型
            console.log(e);
      }
      
    • try...catch(e)结构
      • try...catch主要作用:是对错误进行处理,选择是否往下执行
      • catch(e) 接受的参数表示try代码块抛出的值,任意类型,因为throw可以抛出任意类型
      • 注意:catch捕获错误后,程序不会中断,会按正常流程继续执行下去
        try {
          throw "出错了";
        } catch (e) {
          console.log(111);
        }
        console.log(222);
        // 111
        // 222 还是会正常执行
      
      • try...catch还可以嵌套try...catch
    • try...finally结构
      • try...catch结构允许在最后添加一个finally代码块,表示不管是否出现错误,都必需在最后运行的语句
      • 如果在try...catch中有return语句,会在finally执行完毕后才会去执行return后面的语句
    Error.png

    (二) 六大设计原则

    • 单一职责原则 Single Responsibility Principle
    • 开放封闭原则 Open Closed Principle
    • 里氏替换原则 Liskov Substitution Principle
    • 迪米特法则(最少知识原则) Law of Demeter
    • 接口隔离原则 Interface Segregation Principle
    • 依赖倒置原则 Dependence Inversion Principle

    (2.1) 单一职责原则

    • 关键词
      • ( 单一 ) ( 职责 )
      • 如果我们有两个动机去改写一个方法,那么这个方法就具有两个职责
    • 单一职责原则
      • 一个方法只做一件事
      • 这样需求变迁,则只会影响到最小粒度的方法,其他方法不会受到影响
    • 如何划分职责???
      • 两个完全不一样的功能,不应放在一个类中
      • 一个类中应该是 ( 一组相关性很高的函数,或者数据的封装 )
      • 如果随着需求的变化,有两个职责总是同时变化,那就不必分离他们
      • 即使两个职责已经被耦合在一起,但它们还没有发生改变的征兆,那么也许没有必要主动分离它们
    • 单一职责原则的优优缺点
      • 优点
        • 降低了单个类或者对象的 ( 复杂度 )
        • 按照 ( 职责 ) 把类和对象分解成 ( 更小粒度 ),有利于 ( 代码复用 ) ( 单元测试 ) 等
        • 当一个职责变化时,不会影响到其他职责
      • 缺点
        • 增加了代码复杂度
        • 增加了对象之间相互联系的难度

    (2.2) 开放封闭原则

    • 关键词
      • 开放-封闭原则,满足类,对象,模块,函数是 ( 可扩展的 ),但是是 ( 不可修改的 )
      • 即当需要给程序添加功能或者修改功能时,可以尝试添加代码,但是不能去修改源代码
    • 案例
      • 用对象的 ( 多态 ) 来消除 ( 条件分支 )
      • 将 ( 稳定不变的部分 ) 和 ( 容易变化的部分 ) 分开,则在系统演化迭代的过程中,只需要去修改经常变化的部分,变化的部分使用 ( 钩子函数 ) 和 ( 回调函数 )
    • 优点
      • 原来的代码不用修改
      • 可以复用原来没有修改的代码
    • 开放封闭原则总结
      • 不管是具体的各种设计模式,还是更抽象的面向对象设计原则,比如单一职责原则、最少知识原则、依赖倒置原则等,都是为了让程序遵守开放-封闭原则而出现的

    (2.3) 最少知识原则

    • 概念
      • 一个 ( 对象,类,模块,函数,变量 ) 等应该对其他对象有最少的了解,减少对象之间的耦合

    (2.4) 里氏替换原则

    • 概念
      • 所有引用 ( 基类 ) 的地方,必须能够使用其 ( 子类 ) 直接替换
    • 特点
      • 子类必须实现父类的所有方法,即继承父类所有的方法和属性
      • 子类可以有自己的属性和方法 ( 重写或者添加新的属性 )
      • 覆盖或者实现父类的方法时,入参可以被放大,输出可以被缩小

    (2.5) 依赖倒置原则

    • 模块间依赖通过抽象(比如:接口interface)发生,实现类(实现类可以去实现接口中的方法)之间不直接依赖
    • ( 实现类 ) 依赖于 ( 接口或抽象类 )
    • ( 接口或者抽象类 ) 不依赖于 ( 实现类 ) - 什么是抽象类类?
    • 什么是抽象类类?
      • 如果以一个类中没有足够的信息来描绘一个具体的对象,这样的类就是抽象类
      • 抽象类不能实例化对象,所以抽象类必须被继承才能使用

    (2.6) 接口隔离原则

    • 客户端不应该依赖于它不需要的接口
    • 建立单一的接口

    (三) 工厂模式

    (3.1) 简单工厂模式

    • 在简单工厂模式中,是由工厂来创建产品的,即是老板也是服务员
    abstract class Animal {  // 抽象类不需要自己实现方法
      constructor(public name: string) {
        // this.name = name
      }
    }
    class Cat extends Animal { } // 继承
    class Dog extends Animal { } // 继承
    
    // 简单工厂模式
    // 通过传入 (类型) 在工厂类中根据传入的类型,分别调用给构造函数去生产对应的实例
    class AnimalFactory {
      static create(name: string) { // static 静态方法可以通过类本身去调用
        switch (name) { // 根据传入的类型,调用不同的构造函数,创建不同的实例
          case 'cat':
            return new Cat('cat')
          case 'dog':
            return new Dog('dog')
          default:
            return new Error('出错了')
        }
      }
    }
    
    const dog = AnimalFactory.create('dog') // 生产dog
    const cat = AnimalFactory.create('cat') // 生产cat
    console.log(dog)
    console.log(cat)
    

    (3.2) 工厂方法模式

    • 在简单工厂模式中,是由工厂来创建产品的,即是老板也是服务员
    • 在工厂方法模式中,不再由工厂来创建产品,而是先创建具体的工厂,然后具体的工厂来创建产品
    • 即由原来的老板做,变成了发命令让别人来做
    • 即由工厂命令其他工厂来生产产品
    abstract class Animal { // ----------------------------------------- 产品
      // 抽象类不需要自己实现方法
      constructor(public name: string) {
        // this.name = name
      }
    }
    class Cat extends Animal { } // ------------------------------------ 具体产品 cat
    class Dog extends Animal { } // ------------------------------------ 具体产品 dog
    
    abstract class AnimalFactory { // ---------------------------------- 具体类型的工厂抽象类
      abstract createAnimal(): Animal
    }
    class CatFactory extends AnimalFactory { // ------------------------ 创建cat的工厂,实现类
      // 实现类 实现 抽象类的方法
      createAnimal() {
        return new Cat('cat')
      }
    }
    class DogFactory extends AnimalFactory { // ------------------------ 创建dog的工厂,实现类
      // 实现类 实现 抽象类的方法
      createAnimal() {
        return new Dog('dog')
      }
    }
    
    class Factory { // ------------------------------------------------- 工厂类
      // --------------------------------------------------------------- 根据类型,让具体的工厂去生产产品
      static create(name: string) { // static 静态方法可以通过类本身去调用
        switch (name) { // 根据传入的类型,调用不同的构造函数,创建不同的实例
          case 'cat':
            return new CatFactory().createAnimal()
          case 'dog':
            return new DogFactory().createAnimal()
          default:
            return new Error('出错了')
        }
      }
    }
    
    const dog = Factory.create('dog')
    const cat = Factory.create('cat')
    console.log(dog)
    console.log(cat)
    

    (3.3) 抽象工厂模式

    abstract class Cat {}
    abstract class Dog {}
    class ChinessCat extends Cat {} // 中国猫
    class ChinessDog extends Dog {} // 中国狗
    class EnglishCat extends Cat {} // 美国猫
    class EnglishDog extends Dog {} // 美国狗
    
    abstract class AnimalFactory { // ------------------------------- 抽象工厂类 => 抽象动物工厂
      abstract createCat(): Cat
      abstract createDog(): Dog
    }
    
    class ChineseAnimalFactory extends AnimalFactory { // ----------- 实现类 => 中国动物工厂
      createCat() {
        return new ChinessCat()
      }
      createDog() {
        return new ChinessDog()
      }
    }
    
    class EnglishAnimalFactory extends AnimalFactory { // ----------- 实现类 => 美国动物工厂
      createCat() {
        return new EnglishCat()
      }
      createDog() {
        return new EnglishDog()
      }
    }
    
    const chineseAnimal = new ChineseAnimalFactory() // ------------- 中国动物
    const chineseCat = chineseAnimal.createCat() // ----------------- 中国猫
    console.log(chineseCat)
    

    (四) 适配器模式 adapter

    • 适配器模式又称包装器模式,将一个类的接口转化为用户需要的另一个接口,解决类或对象之间接口不兼容问题
    • 旧的接口和使用者不兼容
    • 中间加一个适配器转换接口
    • 总结
      • 适配器模式就是为了解决 ( 适配两个以上接口不兼容的问题 ) 和 ( 外观模式 ) 的核心思路保持一致
      • 适配器模式adapter,也被称作 ( 包装器模式wrapper )
        • 本质上是在原有逻辑上再包装一层
        • 类比 ( 高阶组件 )
    1
    // Rmb 是需要被适配的类
    class Rmb {
      output() {
        return '100rmb'
      }
    }
    
    abstract class Money {
      abstract transform(): string
    }
    
    class MoneyAdaptor extends Money { // 适配器实现类
      rmb: Rmb
      constructor(rmb: Rmb) {
        super()
        this.rmb = rmb
      }
      transform() {
        return this.rmb.output() + '转成美元'
      }
    }
    
    const dollar = new MoneyAdaptor(new Rmb())
    console.log(dollar.transform());
    
    2
    适配器模式在javascript中的运用 
    - 概念:适配器就是将不适配东西适配起来,比如:手机转接头,转换器等
    - 案例:将 ( 小写字符串 ) 适配成 ( 大写字符串 )
    ---
    
    class Lower {
      getSize = () => "size";
    }
    
    class Adapter {
      lower = new Lower().getSize();
      getSize = () => this.lower.toUpperCase(); // 转成大写
    }
    
    const upper = new Adapter().getSize();
    

    (4.1) 适配器模式有哪些运用

    • axios中区分浏览器和node端时的adaptor函数
    • vue中的computed计算属性

    (4.2) 适配器模式应用案例 - axios

    • axios中的adapter函数就会根据系统的类型去调用不同的请求方法,比如浏览器中使用XMLHttpRequest,而在node环境中使用http模块,从而抹平差异化
    • axios源码中的adapter
    1. var adapter = config.adapter || defaults.adapter;
    
    2. adapter(config).then() // 不管是什么环境,浏览器或者node都返回一个promise
    
    3. defaultes.adapter 如下
    var defaults = {
      adapter: getDefaultAdapter()
    }
    function getDefaultAdapter() {
      var adapter;
      if (typeof XMLHttpRequest !== 'undefined') {
        adapter = require('./adapters/xhr'); // 浏览器环境
      } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
        adapter = require('./adapters/http'); // node环境
      }
      return adapter;
    }
    
    • 手写一个axios中的adapter函数
    axios({
      method: "GET",
      url: "www.baidu.com",
    }).then(
      (value) => console.log(value),
      (reason) => console.error(reason)
    );
    // 设计模式yuanze
    // 1. 单一职责原则 => 一个函数只实现一个逻辑
    // 2. 开放封闭原则 => 可扩展,不可修改
    // 3. 最少知识原则 => 模块,函数,方法,变量都要尽量依赖其他类和对象
    // 4. 里氏替换原则 => 基类可以用子类直接替换
    // 5. 接口隔离原则
    // 6. 依赖倒置原则 => 实现类 依赖 接口或者抽象类
    
    // 浏览器端
    function xhrAdaptor(config) {
      const { method, url } = config;
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method, url, true);
        xhr.setRequestHeader("Content-Type", "application/json"); // setRequestHeader 必须是在 open之后 send之前
        xhr.responseType = "json";
        xhr.onreadystatechange = function () {
          if (xhr.readyState !== 4) {
            return;
            // readyState
            // 0 UNSENT ------------- xhr实例已经被创建,open()方法未被调用
            // 1 OPEND -------------- open()方法被调用,send()方法未被调用,setRequestHeader() 可以被调用
            // 2 HEADERS_RECEIVED --- send()方法被调用,响应头和响应状态已经返回
            // 3 LOADING ------------ 响应体(response entity body)正在下载中,此状态下通过 xhr.response 可能已经有了响应数据
            // 4 DONE --------------- 整个数据传输过程已经完成,无论本次请求是成功还是失败
          }
          if (xhr.status === 200) {
            // readyState === 4 && status === 200
            return resolve(xhr.responseText);
          } else {
            return reject(new Error(xhr.statusText));
          }
        };
        xhr.onerror = function () {
          console.error("error");
        };
        // xhr.onload = function() {
        //     if (xhr.status > 200 && xhr.status < 300 || xhr.status === 304) {
        //         return resolve(xhr.responseText)
        //     }
        // }
        xhr.send();
      });
    }
    
    // 服务器端
    function httpAdaptor(config) {
      const { method, url } = config
      const http = requrie('http')
      const {hostname, port, path} = url.parse(url)
      return new Promise((resolve, reject) => {
        // http模块
        const options = {
          method,
          hostname,
          port,
          path
        };
        let req = http.request(options, function (response) {
          let chunks = [];
          response.on("data", function (chunk) {
            chunks.push(chunk);
          });
          response.on("end", function () {
            const result = Buffer.concat(chunks).toString();
            return resolve(result);
          });
        });
        req.on("error", function (error) {
          return reject(error);
        });
      });
    }
    
    
    function getDefaultAdaptor() {
      // ---------------------- 适配器模式,根据环境调用不同的方法
      let adaptor;
      if (typeof XMLHttpRequest !== "undefined") {
        // ------ 浏览器环境
        adaptor = xhrAdaptor;
      }
      if (typeof process !== "undefined") {
        // ------------- node环境
        adaptor = httpAdaptor;
      }
      return adaptor;
    }
    
    function axios(config) {
      const adaptor = getDefaultAdaptor();
      return adaptor(config);
      // axios返回值就是一个promise对象,无论是浏览器端还是node端
    }
    

    (五) 装饰器模式

    • 装饰器模式是为已有功能更多功能的一种方式,是一种结构型设计模式,是对原有类进行扩展
    • 是一种依靠 ( 组合 ) 来实现 ( 类的功能扩展 ),并且支 ( 持多层嵌套 ) 的设计模式
    • 如果直接添加逻辑会违反 ( 单一职责原则 ) 和 ( 开放封闭原则 )
    • 常用的装饰器有 ( 类装饰器 ) ( 属性装饰器 ) ( 方法装饰器 ) ( 参数装饰器 )
    abstract class Shape { // 形状抽象类
      abstract draw(): void;
    }
    
    class Circle extends Shape { // 形状子类 圆
      draw() {
        console.log("绘制圆");
      }
    }
    
    class Rectagle extends Shape { // 形状子类 矩形
      draw() {
        console.log("绘制矩形");
      }
    }
    
    abstract class Color extends Shape { // Color 也继承 Shape
      constructor(public shape: Shape) {
        super();
      }
      abstract draw(): void;
    }
    
    class Red extends Color {
      draw() {
        this.shape.draw(); // 调用传入的参数实例的draw方法,在new Red()时传入的参数是new Circle()
        console.log("绘制红色");
      }
    }
    class Yellow extends Color {
      draw() {
        this.shape.draw();
        console.log("绘制黄色");
      }
    }
    
    const redCircle = new Red(new Circle());
    redCircle.draw() // 调用实例上的draw方法
    
    image.png

    (5.1) 装饰器模式在前端中的运用

    • 2021/09/22 更新
    • 表单验证
      • 在 ( 表单提交时,先做表单验证 ) 的工作
      • 核心函数:submit submit.before validate
    /**
     * 借助装饰者模式,很容易衍生出 AOP 面向切面编程的概念
     * - 场景:典型场景就是对表单的验证,我们将把表单输入逻辑校验的 validata 函数融入到 before 逻辑当中
     * - 具体:
     *   1. 在提交表单时,执行 ( submit ) 函数,因为在 ( Function.prototype.before ) 挂载了 ( before ) 函数,被所有实例函数所继承
     *   2. 我们并不直接submit函数,而是调用 ( submit.before ) 从而在 sumbit 之前执行验证函数 ( validate ) 从而在执行sumbit之前做表单验证功能
     */
    Function.prototype.before = function(beforefn) {
      const self = this
      return function() {
        if (beforefn.apply(this, arguments) === false) return // 表示执行验证逻辑时,验证未通过
        return self.apply(this, arguments) // this是调用before方法时所在的对象,是 submitBtn 在调用before,所以在了的 self 就是 submitBtn 函数
      }
    }
    
    const validate = function() {
      // 表单验证逻辑
    }
    
    const formSubmit = function() {
      // 表达提交逻辑
      ajax('http:// xxx.com/login', param)
    }
    
    submitBtn.onclick = function() {
      formSubmit.before(validate)
    }
    

    资料

    设计原则1 https://juejin.cn/post/6844904025565970439
    设计原则2 https://juejin.cn/post/6847902225717854215
    装饰器模式1 https://juejin.cn/post/6914235611545092109
    装饰器模式2 https://juejin.cn/post/6881897639001751560

    vscode=>codeRunner=>ts-node出现乱码解决方案如下
    https://github.com/formulahendry/vscode-code-runner/issues/632

    相关文章

      网友评论

          本文标题:[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法)

          本文链接:https://www.haomeiwen.com/subject/hokdoltx.html