邏輯管理:解決方案(一) - 關於前端邏輯管理的設計和實現

切入思考點

  組件化,解決了一組能夠複用的功能,咱們可以使用通常的開源的公共組件,也能夠針對咱們特殊業務場景,沉澱出符合本身業務的業務組件;
  工程化,解決了可控和規範性的功能,咱們可以使用開源的一些腳手架好比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

相關文章
相關標籤/搜索