程序设计&编程思想

装饰器模式

修饰器模式/代理模式

核心概念:

为类对象不通过继承的方式 添加的新的功能(方法),而不改变原有的结构和功能

类当中引入修饰器模式

  1. class Parent {
  2. constructor() {
  3. this.name = 'zhangsan';
  4. }
  5. //语法
  6. @readOnly
  7. say(){}
  8. }

问题:ES6的类继承有什么短板?

当它仅仅作为一个功能而不是一个主体时,但是又想继承去使用它的属性和方法,会显得它的功能不够强大,一旦想继承多个功能时,就必须要继承多个类去实现,显得不够灵活,此时可以使用装饰器模式去解决

问题:什么时候会用到装饰器模式?

当某一个类的上方加上@方法名时的装饰器写法可以对其目标target类进行装饰,从而健全这个类所需要的属性和方法,同时,装饰器不仅可以修饰类,也可以修饰函数

问题:装饰器有什么好处?

不用继承就可以使用公共的属性和方法(新增附加功能),它也是一个纯函数,也不用传参,使用的时候非常的灵活(在任何地方也可以使用),也可以实时无数个装饰器去装饰一个类

问题:如何写一个装饰器函数?

定义一个函数,里面有三个参数:

  • target: 目标类
  • key:键名
  • descriptor:描述符
  1. export default(target) => {
  2. target.plus = function(num1, num2){
  3. return num1 + num2;
  4. }
  5. target.minus = function(num1, num2){
  6. return num1 - num2;
  7. }
  8. ...
  9. }

注意:使用装饰器写法时,得在项目中新增es7依赖

迭代器模式

问题:什么是遍历?

它实际上是从一个容器里拿到所有的东西出来,它的缺点是不能对每一次遍历的过程中对遍历的结果进行处理

问题:什么是迭代?

在本次遍历的过程中进行一次程序上的输出,它仅仅是遍历中的其中一步

问题:什么叫项目迭代?

根据本次的结果进行一次更新(修复),或者是小功能的叠加,在遍历过程中进行某一次输出时,必须用到迭代器

问题:迭代器有什么作用?

它可以避免遍历一次性的执行全部(如for of,for循环),从而一次一次的去执行

问题:迭代器的缺点?

不能单独使用,它建立在遍历的基础之上,需要结合生成器使用

问题:生成器是什么?

它是生产迭代器(iterator)的函数,利用遍历生产一个迭代器function * test(){},函数内部的yield关键字可以暂停遍历,并可以加工处理

关于迭代器yeild关键字:

它可以在遍历时暂停迭代操作

关于迭代器自带的next方法:

它调用时会执行一次迭代操作,迭代没有完成时,它返回的是一个对象{value: 1, done: false},完成时,它返回一个对象{value: undefined, done: true}

  1. //es6写法:
  2. function * test(arr){
  3. for(var item of arr){
  4. yield item;
  5. }
  6. }
  7. let iterator = test(arr);
  8. console.log(iterator.next());
  9. console.log(iterator.next());
  10. ...
  1. //es5写法: 不用*写法
  2. function generator(arr){
  3. var i = 0;
  4. return {
  5. next: function(){
  6. //最后一项时为true
  7. var done = (i >= arr.length),
  8. //非最后一项时为当前项 并i++
  9. //最后一项时为undefined
  10. value = !done ? arr[i++] : undefined;
  11. return {
  12. value: xxx,
  13. done: false
  14. }
  15. }
  16. }
  17. }

应用场景:

如底层封装时经常用到,如打印日志等操作,它具有记忆功能

  1. //场景1
  2. var functions = [
  3. function test1(){},
  4. function test2(){ return false },
  5. function test3(){},
  6. ]
  7. //当某一个函数返回false时中止遍历
  8. for(let item of functions){
  9. if(!item()){
  10. break;
  11. }
  12. }
  1. //场景2 koa中的洋葱模型中间件
  2. var fns = [
  3. function test1(next) {
  4. console.log('test1');
  5. next();
  6. },
  7. function test2(next) {
  8. console.log('test2');
  9. // next();
  10. },
  11. function test3(next) {
  12. console.log('test3');
  13. next();
  14. }
  15. ];
  16. //当某一个函数没有写next()执行时迭代中断
  17. function* generator(arr) {
  18. for (let i = 0; i < arr.length; i++) {
  19. //中止每次遍历出来的元素
  20. yield arr[i];
  21. }
  22. }
  23. const iterator = generator(fns);
  24. //实现全部函数都执行
  25. nextDo(iterator.next());
  26. function nextDo(n) {
  27. // console.log(n);
  28. //{done: false, value: ƒ test1(next)}
  29. n.value(function () {
  30. const n = iterator.next();
  31. // console.log(n);
  32. //{done: false, value: ƒ test2(next)}
  33. //未迭代完成时
  34. if (!n.done) {
  35. nextDo(n);
  36. } else {
  37. //迭代完成时
  38. return;
  39. }
  40. });
  41. }

观察者模式

案例:购物车(课程列表)

页面包含课程列表和待报名课程两个模块

实现:

点击课程列表其中的项目后会与待报名课程模块立即会有联动(一个模块里有两个功能模块)

问题1:为什么需要两个子模块分开处理?

因为如果两个模块一起编写,在修改其中模块时会影响另一个模块,而且不方便维护,且逻辑之间杂乱不清

问题2:两个分开模块之间如何实现数据联动管理?

利用观察者模式实现管理数据更新

观察者原理:

每一个观察者实际上是一个函数,一旦触发事件,执行一个函数完成一个程序,当一个程序分为1,2,3件事情,那么这个程序就为一个observers,当执行程序123(observers)时不能单独执行,利用obsevers里面的notify通知特性,将123一次性执行,真正事件触发时仅仅执行的是notify函数

也就是说,希望123函数作为一个个obsevers放入数组里,统一用notify一次去执行,在项目里,点击单击事件时触发执行notify函数,然后notify里面所有的observers会依次执行

  1. //Observers.js
  2. observers = [fn, fn, fn];
  3. add() -> fn -> observers
  4. //执行notify()会使每一项observer执行
  5. notify() -> forEach -> observers -> item();

项目渲染原理:

数据 (需要处理) -> 容器 -> 渲染 -> 插入节点

观察者管理的好处:

一个notify函数的执行同时 把handle底下的所有函数同时执行,实现参数实时共享 好处是模块功能同时联动,实现高内聚的代码编写

项目结构:

  1. ├─package.json
  2. ├─webpack.config.js
  3. ├─src
  4. | ├─index.html
  5. | ├─utils
  6. | | ├─Observer.js - 观察者类 驱动
  7. | | tools.js - 工具类方法集合
  8. | ├─templates - 模板文件
  9. | | ├─cartItem.tpl
  10. | | courseItem.tpl
  11. | ├─js
  12. | | ├─index.js - 网页入口文件
  13. | | ├─ShoppingCart
  14. | | | ├─index.js - 主程序入口文件
  15. | | | ├─Course
  16. | | | | ├─Event.js - 管理子模块的绑定事件
  17. | | | | ├─Handle.js - 管理子模块里绑定事件函数底下的所有逻辑方法
  18. | | | | ├─index.js - 子模块入口文件
  19. | | | | Render.js - 渲染函数
  20. | | | ├─Cart
  21. | | | | ├─Event.js
  22. | | | | ├─Handle.js
  23. | | | | ├─index.js
  24. | | | | Render.js
  25. | ├─data - 数据
  26. | | ├─cart.js
  27. | | course.js

源码地址:

https://gitee.com/kevinleeeee/data-response-observers-shoppingcart-demo

工厂模式

案例:模态框

使用工厂设计模式做一个插件库里面有一个模态框组件(仅展示设计模式用)

技术:

  • express + 原生js + 工厂模式

功能:

  • 按钮1:成功态
  • 按钮2:警告态
  • 按钮3:失败态

问题:工厂设计解决什么问题?

希望有一个模块ModalFactory类去可以根据状态的不同去实例化创建,并且实现点击切换样式颜色功能的同时还可以拓展其他功能,便于日后维护

继承关系:

  1. Modal <- extend - SuccessModal/WarningModel/ErrorModal
  2. ModalFactory -> new SuccessModal/WarningModel/ErrorModal -> 拿到实例对象 -> 实例对象.create(标题, dom节点自定义属性)

项目目录: https://gitee.com/kevinleeeee/factory-design-ui-plugin-modal-demo

状态模式

将一个类抽象多个状态的类,这些类和主类保持了继承关系和依赖关系

问题:什么是solid设计原则?

  • 单一职责原则(胖函数减肥)
  • 开放封闭(拓展开发修改)
  • 里氏替换
  • 接口开放
  • 依赖倒置

享元模式

享受元模式(fly weight),它是解决性能优化的有效射击模式,通过共享属性状态(属性和方法)的方式来达到减少创建对象的目的,以此来提升性能优化

状态管理器:

一个对象方法专门来负责添加内部(实例添加)和外部状态(动态赋值)

问题:为什么区分内部和外部状态?

内部状态通过工厂(Factory)的方式来创建,外部状态通过构造函数(Manager)的方式来设置

关于单例模式:

一种方式,函数执行完毕时返回一个实例

问题:享元模式如何实现性能优化?

它循环添加内部状态时都像单例模式那样返回同一个实例以达到性能优化

派发器模式

Dispatcher派发器模式来改造组件的逻辑部分

问题:如何的去根据需求去更改数据?

methods操作数据 data,更多的不想逻辑写在methods里面,因为一个组件它的逻辑部分非常多的话,methods里面就不好维护了

优点是给更多同步开发的开发者实现方法管理

问题:如何根据一个模式去设计不让method里面的逻辑变得臃肿?

抽离相应的部分,通过type找到一个对应事件,找到事件对应的逻辑,逻辑通过事件类型type去触发派发器,触发数据的改变,这样就横向的把methods拆分出来了

原理:

  1. type -> 事件 -> 逻辑 -> type -> 派发器 -> 数据的更改
  1. //dispatchers > xxx(组件名称).js
  2. export default (ctx) => {
  3. const {
  4. method,
  5. removeItem,
  6. changeCompleted
  7. } = todoReducer(ctx.todoData);
  8. return function (type, arg) {
  9. switch (type) {
  10. case ADD:
  11. ctx.todoData = addItem(arg);
  12. break;
  13. case REMOVE:
  14. ctx.todoData = removeItem(arg);
  15. break;
  16. case COMPLETED:
  17. ctx.todoData.completed = changeCompleted(arg);
  18. break;
  19. default:
  20. break;
  21. }
  22. }
  23. }
  1. //reducer > xxx(组件名称).js
  2. function todoReducer(data) {
  3. function addItem(newItem) {
  4. return data.concat(newItem);
  5. }
  6. function removeItem(id) {
  7. return data.filter(item => item.id !== id);
  8. }
  9. function changeCompleted(id) {
  10. return data.map((item) => {
  11. // console.log(item);
  12. if (item.id === id) {
  13. item.completed = !item.completed;
  14. }
  15. });
  16. }
  17. return {
  18. addItem,
  19. removeItem,
  20. changeCompleted
  21. }
  22. }
  23. export default todoReducer;
  1. //actions > xxx(组件名称).js
  2. const ADD = 'ADD',
  3. REMOVE = "REMOVE",
  4. COMPLETED = 'COMPLETED';
  5. export {
  6. ADD,
  7. REMOVE,
  8. COMPLETED
  9. }
  1. //index.vue块组件
  2. methods: {
  3. dispatch(...args) {
  4. dispatch(this)(...args);
  5. }
  6. },

案例 1:通过加减更改结果

  1. //项目结构
  2. ├─package.json
  3. ├─webpack.config.js
  4. ├─src
  5. | ├─App.vue
  6. | ├─main.js
  7. | ├─reducers
  8. | | counter.js - 所有couter组件的的业务逻辑都在这里进行管理
  9. | ├─dispatchers
  10. | | counter.js - 派发器导出接收一个组件上下文的函数/定义methods里面的许多方法
  11. | ├─components
  12. | | ├─Counter
  13. | | | ├─Button.vue
  14. | | | ├─index.vue - 块组件管理组件/使用派发器
  15. | | | Result.vue
  16. | ├─actions
  17. | | counter.js - 专门管理counter组件里面事件的类型 PLUS/MINUS
  18. ├─public
  19. | index.html

源码地址:

https://gitee.com/kevinleeeee/dispatcher-vue2.x-demo1


案例 2:计算器

技术:派发器

  1. //项目结构
  2. ├─src
  3. | ├─App.vue
  4. | ├─main.js
  5. | ├─reducers
  6. | | calculator.js
  7. | ├─libs
  8. | | utils.js
  9. | ├─dispatchers
  10. | | calculator.js
  11. | ├─components
  12. | | ├─Calculator
  13. | | | ├─CalButton.vue
  14. | | | ├─CalInput.vue
  15. | | | ├─CalResult.vue
  16. | | | index.vue
  17. | ├─actions
  18. | | calculator.js
  19. ├─public
  20. | index.html

源码地址:

https://gitee.com/kevinleeeee/dispatcher-calculate-vue2.x-demo2


案例 3:Todolist

  1. //项目结构
  2. ├─src
  3. | ├─App.vue
  4. | ├─main.js
  5. | ├─reducers
  6. | | todoList.js
  7. | ├─dispatchers
  8. | | todoList.js
  9. | ├─components
  10. | | ├─TodoList
  11. | | | ├─index.vue
  12. | | | ├─TdForm.vue
  13. | | | ├─TdTitle.vue
  14. | | | ├─TdList
  15. | | | | ├─index.vue
  16. | | | | ListItem.vue
  17. | ├─actions
  18. | | todoList.js
  19. ├─public
  20. | index.html

源码地址:

https://gitee.com/kevinleeeee/dispatcher-todolist-vue2.x-demo3