逻辑管理:解决方案(一) - 关于前端逻辑管理的设计和实现

切入思考点

  组件化,解决了一组能够复用的功能,咱们可以使用通常的开源的公共组件,也能够针对咱们特殊业务场景,沉淀出符合本身业务的业务组件;
  工程化,解决了可控和规范性的功能,咱们可以使用开源的一些脚手架好比vue-cli、create-react-app等,或者公司内部本身沉淀的内部脚手架解决方案;
  可是谁来解决散落在各个模块和工程中的逻辑?怎样去避免硬代码编程,减小逻辑的后期维护和成本等等,也是一个须要考虑的点。前端

观察代码

  首先能够从一个客观角度去分析这份代码,review这份代码,能够看出不少问题,好比:vue

  • 开头的配置参数和类型检查的配置,代码占了很大篇幅,是否能够抽离到配置文件管理里去维护?
  • tools工具类是否能够进行重构,一个tools聚合了不少不一样类型的辅助方法,后期增加是否会持续臃肿,是否能够经过分类概括,tools管理更清晰明了
  • tools的内部工具,是否能够拆分红只作一件事和多件事共同完成一件事方式?
  • 太长的函数,是否有拆分的可能,加强可读性要求?
  • 不少方法依赖自身对象的其余方法,整个链路的流转复杂多变,牵一发动全身。
  • 代码能力划分不明确,通用和非通用没有明确界定
  • 对外暴露能力的代码重复度比较高
  • ......

  当时最初写这份代码还作过简单的分类,有点逻辑管理的浅显意识。可是咱们能够看看咱们本身真实用于生产的公司的项目,多人维护,协同开发、业务增加等,到最后已经彻底不可控,逻辑动都不敢动,只敢打补丁,愈来愈臃肿。下面就是我以前针对咱们内部项目一小块作的一块分析,这些都真实存在几乎全部人的代码里,是咱们存在的痛点。react

  • 单独时间处理函数,是否能够抽离到公用逻辑中,基于原型链的属性,是否会污染和覆盖原型链属性等
  • 业务交互设计功能,是否能够封装到独立函数中?
  • 枚举统一抽离管理?
  • 请求抽离统一管理?
  • 数据的转换赋值处理?
  • 复杂文案拼装,抽象到函数中,提升可读性?减轻复杂度?
  • 多重逻辑判断是否可简化表达式?分解复杂条件,合并行为一致?
  • ....

前端对业务作了什么?

  基于以前对代码的分析,堆积了不少问题,说明这块确实是咱们的痛点。那么这些痛点归根究底是咱们作了什么致使?前端对业务到底作了哪些方面的东西?git

  1. 获取业务数据(业务规则下的数据获取)
  2. 数据处理(可细分转换,格式化,校验等等)
  3. 业务判断(针对业务场景,每一个场景下须要作什么)
  4. 业务数据提交(业务规则产出的数据的记录)
  5. 业务交互功能(在业务规则下,须要怎么作,作怎样的功能)
  6. 业务展现(在业务场景下,合理的show出业务的形态)
  7. ......(暂时只想到这些领域,若有遗漏欢迎补充)

  以上,几乎囊括了前端在业务领域,所须要作的全部事情,也是咱们的全部的逻辑。github

对逻辑的深刻思考

  咱们须要这些逻辑的堆砌去完成咱们须要的东西,其实观察每一小块业务代码,都是由一条条最简单的逻辑规则,一步步流转到最后咱们所须要的结果的,就跟咱们作的思惟脑图同样,一个流程节点都是一个小逻辑。一个业务的开始,到一个业务的结束,都是由每一个最小的逻辑点组成的。ajax

  so,咱们能不能站在一个全局的角度去看整个业务,能不能把每一个流程节点打碎成一个最小的原子,全部的业务逻辑,都是从最小的原子一个一个组装起来的,这样,咱们就能更专一于最小的逻辑。咱们所作的任何业务都是由原子拼起来。这样就能够从基础去hold住任何逻辑,无论复杂和简单。vue-cli

  咱们也能够参考,在Java或者其余后端语言里,设计最初是最理想。它们都但愿,个人世界就和现实世界同样,都是由最小的颗粒去组装我想要的设计的世界。因此一个class表明了一类事情,一个function表明了一件事。不管大家上面怎么玩,我都能支持大家去组装大家要的世界,大家要作的任何复杂的事。因此,逻辑处理其实也是这样的,把任何逻辑打成最小颗粒,经过拼接,组装,去支撑上层的任何业务逻辑。npm

如此以后,设想以下场景:编程

  1. 只关心原子逻辑,去丰富原子逻辑
  2. 业务逻辑,在原子提供的逻辑上适应任何业务规则,经过组装去产出任何业务代码
  3. 业务规则变化下,小变化,直接替换一个逻辑节点,替换插槽。大变化,从新组装另外一条业务线。
  4. 整个链路数据流转清晰可追踪
  5. ...

理想设计架构图

简单摸索设计思路

  原子逻辑:对象的基类,管理全部注入原子后端

  组合逻辑:继承原子,组合,输出

  对外接口:解析配置,调用原子和组合类管理、抛出生产结果

思路图以下:

基类设计代码

// 原子管理类,管理全部原子逻辑
class Atom {

  /*
  * 注入原子逻辑,以属性的方式管理
  *   objArr: 原子逻辑数组
  * */
  setBasics(objArr) {
    objArr.forEach(x => {
      this[x.name] = x.assembly
    })
  }

  /*
  * 生产组装类所须要的原子
  *   param
  *     useBasics:组装类,所须要继承的原子
  *       支持type: String - 指定一个、Array - 指定多个、无(undefined)- 全部
  *
  *   return
  *     output:生产出的原子逻辑
  * */
  machiningBasics(useBasics) {
    let output = {}
    if (useBasics) {
      if (Array.isArray(useBasics)) {
        useBasics.forEach(x => {
          Object.assign(output, this[x])
        })
      } else {
        Object.assign(output, this[useBasics])
      }
    } else {
      Object.keys(this).forEach(x => {
        Object.assign(output, this[x])
      })
    }
    return output
  }
}
复制代码

  基类,做为最底层的基础模块,管理全部原子,供上层业务逻辑继承和调用,去组装本身的业务逻辑。该类内部抛出2个方法以下:

  setBasics:做为对原子逻辑的注入。能够持续去丰富底层的原子逻辑(后期是否支持动态注入,再考虑);

  machiningBasics:提供给组装类继承原子的逻辑,输出所须要的底层基础,供上游拼装

组装类设计代码

// 因ES6不支持私有属性,因此将私有属性放到外层

/*
* 生产组装对象,并注入指定做用域
*   param -
*
*   return
*     Temporary:组装对象
*
* */
function makeObject() {
  function Temporary(assembly) {
    for (let key in assembly) {
      this[key] = assembly[key].bind(this)
    }
  }

  return Temporary
}

/*
* 组装中是否透传原子方法
*   param
*     Temporary:组装对象
*     config: 组装的配置
*
*   return
*     output:输出最终逻辑
* */
function isThrough(Temporary, config) {
  // 根据配置,实例化对象
  let temp = new Temporary(config.assembly)
  let output = {}
  for (let key in temp) {
    // 是否开启配置
    if (config.through  === false) {
      // 是不是自身属性
      if (temp.hasOwnProperty(key)) {
        output[key] = temp[key]
      }
    } else {
      output[key] = temp[key]
    }
  }
  return output
}

// 组装类,管理组装和输出。
class Package {

  /*
  * 注入组装配置
  *   param
  *     config:组装配置
  *     prototype:组装所依赖的原子属性
  *
  *   return  生产完成的对象
  * */
  setPackage(config, prototype) {
    let temp = makeObject(config)
    temp.prototype = prototype
    return isThrough(temp, config)
  }
}

export default Package
复制代码

  组装类,经过一系列的原子逻辑组装成一条条所须要的业务逻辑。总体步骤为:生产出组装的对象,经过原型继承装配原子,对外暴露组装结果。就跟工厂同样,生产目标,生产原料,生产产物。组装类对内部抛出一个方法:

  setPackage:根据提供的配置文件以及所需继承的原子,组装出一类业务逻辑。

index入口设计

import Atom from './atom/index'
import Package from './package/index'

// 实例化原子和组装类
const _atom = new Atom()
const _package = new Package()

// 生产原子缓存
let _globalCache = {}

/*
* 对外暴露,注入配置依赖,生产组装
*   param
*     param: 配置参数
* */
export const injection = function (param) {
  _atom.setBasics(param.atom)

  param.package.forEach(x => {
    let prototype = _atom.machiningBasics(x.extends)
    // 缓存组装
    _globalCache[x.name] = _package.setPackage(x, prototype)
  })
}

/*
* 对外暴露,获取生产完成的组装对象
*   param
*     param:获取的目标
*       type:String - 指定一个、Array - 指定多个、 无(undefined) - 所有
*
*   return
*     output:生产结束的对象
* */
export const getMateriel = function (param) {
  let output = {}
  if (param) {
    if (Array.isArray(param)) {
      return param.forEach(x => {
        output[x] = _globalCache[x]
      })
    } else {
      output = _globalCache[param]
    }
  } else {
    output = _globalCache
  }
  return output
}
复制代码

  对外的入口,主要功能为解析配置,组装配置,输出组装结果供使用3大功能。

  injection:标准对外入口,进行逻辑管理的初始化,该方法将全部的原子逻辑注入到原子类里,再经过组装配置,从原子类获取到每一个组装对象所须要继承的原子供组装使用,最后将组装好的逻辑全局存到一个全局的缓存里。

  getMateriel:对外输出生产完成的组装逻辑,暴露出组装结束的结果,可获取全部组装结果,也可单独或者批量获取结果

使用格式规定

默认注入配置(injection方法)
/*
*  injection方法注入对象的格式
*   atom:     全部的原子逻辑
*   package:  组装原子的逻辑
*/
{
  atom: ['原子逻辑1', '原子逻辑2'],
  package: ['组装逻辑1', '组装逻辑2']
}
复制代码
原子逻辑文件格式
/*
*   该格式为原子逻辑的标准格式
*     name:       原子类的名称
*     assembly:   原子的方法存放的对象
*/
export default {
  name: '原子的名称',
  assembly: {
    // 原子逻辑所对外提供的方法
    sendRequest() {
      // do something
    }
  }
}
复制代码
组装逻辑文件格式
/*
*   该格式为组装逻辑的标准格式
*     name:       组装类的名称
*     extends:    组装类须要继承的原子
*     through:    是否透传原子类内部的信息
*     assembly:   原子的方法存放的对象
*/
export default {
  name: '组装类名称',
  extends: '继承原子',      // 支持字符串(单原子)、无(默认继承全部原子)、数组(指定多个原子)
  assembly: {
    // 组装逻辑对外产出的方法,可直接this.来调用继承原子的方法
    getAtom1Promise() {
      // do something...
    }
  }
}
复制代码

DEMO展现(可直接 npm run start 直接跑起来测试)

目录格式

  --src
    |-atom      // 存放原子逻辑的地方
    |-package     // 存放组装逻辑的地方
    |-index.js     // 入口文件

原子逻辑(atom)

atom1.js

// atom1.js
export default {
  name: 'atom1',
  assembly: {
    sendRequest() {
      return new Promise((res, rej) => {
        setTimeout(function () {
          res([1, 2, 3])
        }, 3000)
      })
    }
  }
}
复制代码

atom2.js

// atom2.js
export default {
  name: 'atom2',
  assembly: {
    judgeArray(data) {
      return Array.isArray(data)
    }
  }
}
复制代码
组装逻辑(package)

package1.js

// package1.js
export default {
  name: 'package1',
  extends: 'atom1',
  assembly: {
    getAtom1Promise() {
      this.sendRequest()
        .then(x => {
          console.warn('使用成功', x)
        })
    }
  }
}
复制代码

package2.js

// package2.js
export default {
  name: 'package2',
  through: false,
  assembly: {
    packageLogin() {
      this.sendRequest()
        .then(x => {
          console.warn('判断是不是数组:', this.judgeArray(x))
        })
    }
  }
}
复制代码
入口(index)

index.js

import {injection, getMateriel} from '@fines/factory-js'

import atom1 from './atom/atom1'
import atom2 from './atom/atom2'
import package1 from './package/package1'
import package2 from './package/package2'

injection({
  atom: [atom1, atom2],
  package: [package1, package2]
})

console.warn('组装成功:', getMateriel())

// 测试package1方法
getMateriel('package1').getAtom1Promise()

// 测试package2方法
getMateriel('package2').packageLogin()
复制代码
测试结果

github托管

地址

  连接:传送门 感受有参考意义能够点个star,内部正在使用踩坑中

Issues

  连接:传送门有问题,有意见,你就说😆

demo地址

  连接:传送门

  PS:可直接 npm run start 直接跑起来测试

npm发布

包名

  @fines/factory-js

安装

  npm i @fines/factory-js

注明

  fines做为一个新的注册的组织,这里将写一些更美好的东西,之后全部能变得更美好的代码都将发布到这个包下面(更重要一些包名已经没法使用,可是组织能够无限制)

后记

之前在逻辑管理领域作过相关的摸索和思考,以下:

  1. 思考书写更好可控的代码
  2. 探索复杂前端业务的开发与设计

  在以前的摸索基础上,更深刻的思考,才最终产出这个逻辑的解决方案,仅供你们参考,后面仍将持续完善该方案。

  社区有人说,这不是你前端作的事,不是你的活,作这个干啥?听完这句话,总感受有点别扭。

  在我看来,咱们每一个人都是一个架构师,不断地在架构本身的代码。不停的去认知世界的样子,认知自我。咱们都不是最完美的,有好也有坏。去发现自身痛点,对痛点进行分析,进行思考,找出最终的根源,而后再去思考如何去解决这个痛点,尝试,摸索,失败,阶段性胜利,再继续。就这样一路走来,坚信终有收获。共勉!

支持信息

职业目标:全栈架构师
同步更新:博客园知乎知乎专栏github

相关文章
相关标签/搜索