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


切入思考點

組件化,解決了一組可以復用的功能,我們可使用一般的開源的公共組件,也可以針對我們特殊業務場景,沉淀出符合自己業務的業務組件;

工程化,解決了可控和規范性的功能,我們可使用開源的一些腳手架比如vue-cli、create-react-app等,或者公司內部自己沉淀的內部腳手架解決方案;

但是誰來解決散落在各個模塊和工程中的邏輯?怎樣去避免硬代碼編程,減少邏輯的后期維護和成本等等,也是一個需要考慮的點。

 

觀察代碼

首先可以從一個客觀角度去分析這份代碼,review這份代碼,可以看出很多問題,比如:

    • 開頭的配置參數和類型檢查的配置,代碼占了很大篇幅,是否可以抽離到配置文件管理里去維護?
    • tools工具類是否可以進行重構,一個tools聚合了很多不同類型的輔助方法,后期增長是否會持續臃腫,是否可以通過分類歸納,tools管理更清晰明了
    • tools的內部工具,是否可以拆分成只做一件事和多件事共同完成一件事方式?
    • 太長的函數,是否有拆分的可能,增強可讀性要求?
    • 很多方法依賴自身對象的其他方法,整個鏈路的流轉復雜多變,牽一發動全身。
    • 代碼能力划分不明確,通用和非通用沒有明確界定
    • 對外暴露能力的代碼重復度比較高
    • ......

當時最初寫這份代碼還做過簡單的分類,有點邏輯管理的淺顯意識。但是我們可以看看我們自己真實用於生產的公司的項目,多人維護,協同開發、業務增長等,到最后已經完全不可控,邏輯動都不敢動,只敢打補丁,越來越臃腫。下面就是我之前針對我們內部項目一小塊做的一塊分析,這些都真實存在幾乎所有人的代碼里,是我們存在的痛點。 

    • 單獨時間處理函數,是否可以抽離到公用邏輯中,基於原型鏈的屬性,是否會污染和覆蓋原型鏈屬性等
    • 業務交互設計功能,是否可以封裝到獨立函數中?
    • 枚舉統一抽離管理?
    • 請求抽離統一管理?
    • 數據的轉換賦值處理?
    • 復雜文案拼裝,抽象到函數中,提高可讀性?減輕復雜度?
    • 多重邏輯判斷是否可簡化表達式?分解復雜條件,合並行為一致?
    • ....

 

前端對業務做了什么?

基於之前對代碼的分析,堆積了很多問題,說明這塊確實是我們的痛點。那么這些痛點歸根究底是我們做了什么導致?前端對業務到底做了哪些方面的東西?

    1. 獲取業務數據(業務規則下的數據獲取)
    2. 數據處理(可細分轉換,格式化,校驗等等)
    3. 業務判斷(針對業務場景,每個場景下需要做什么)
    4. 業務數據提交(業務規則產出的數據的記錄)
    5. 業務交互功能(在業務規則下,需要怎么做,做怎樣的功能)
    6. 業務展示(在業務場景下,合理的show出業務的形態)
    7. ......(暫時只想到這些領域,如有遺漏歡迎補充)

以上,幾乎囊括了前端在業務領域,所需要做的所有事情,也是我們的所有的邏輯。

 

對邏輯的深入思考

我們需要這些邏輯的堆砌去完成我們需要的東西,其實觀察每一小塊業務代碼,都是由一條條最簡單的邏輯規則,一步步流轉到最后我們所需要的結果的,就跟我們做的思維腦圖一樣,一個流程節點都是一個小邏輯。一個業務的開始,到一個業務的結束,都是由每個最小的邏輯點組成的。

so,我們能不能站在一個全局的角度去看整個業務,能不能把每個流程節點打碎成一個最小的原子,所有的業務邏輯,都是從最小的原子一個一個組裝起來的,這樣,我們就能更專注於最小的邏輯。我們所做的任何業務都是由原子拼起來。這樣就可以從基礎去hold住任何邏輯,不管復雜和簡單。

我們也可以參考,在Java或者其他后端語言里,設計最初是最理想。它們都希望,我的世界就和現實世界一樣,都是由最小的顆粒去組裝我想要的設計的世界。所以一個class代表了一類事情,一個function代表了一件事。無論你們上面怎么玩,我都能支持你們去組裝你們要的世界,你們要做的任何復雜的事。所以,邏輯處理其實也是這樣的,把任何邏輯打成最小顆粒,通過拼接,組裝,去支撐上層的任何業務邏輯。

 

如此之后,設想如下場景:

    • 只關心原子邏輯,去豐富原子邏輯
    • 業務邏輯,在原子提供的邏輯上適應任何業務規則,通過組裝去產出任何業務代碼
    • 業務規則變化下,小變化,直接替換一個邏輯節點,替換插槽。大變化,重新組裝另一條業務線。
    • 整個鏈路數據流轉清晰可追蹤
    • ...

 

理想設計架構圖

image.png

 

簡單摸索設計思路

原子邏輯:對象的基類,管理所有注入原子

組合邏輯:繼承原子,組合,輸出

對外接口:解析配置,調用原子和組合類管理、拋出生產結果

 

思路圖如下:

 

基類設計代碼

// 原子管理類,管理所有原子邏輯
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
  }
}

export default Atom

基類,作為最底層的基礎模塊,管理所有原子,供上層業務邏輯繼承和調用,去組裝自己的業務邏輯。該類內部拋出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展示

目錄格式

-src

  |-atom                // 存放原子邏輯的地方

  |-package          //  存放組裝邏輯的地方

  |-index.js           //  入口文件

 

原子邏輯(atom)

export default {
  name: 'atom1',
  assembly: {
    sendRequest() {
      return new Promise((res, rej) => {
        setTimeout(function () {
          res([1, 2, 3])
        }, 3000)
      })
    }
  }
}
export default {
  name: 'atom2',
  assembly: {
    judgeArray(data) {
      return Array.isArray(data)
    }
  }
}

 

組裝邏輯(package)

export default {
  name: 'package1',
  extends: 'atom1',
  assembly: {
    getAtom1Promise() {
      this.sendRequest()
        .then(x => {
          console.warn('使用成功', x)
        })
    }
  }
}
export default {
  name: 'package2',
  through: false,
  assembly: {
    packageLogin() {
      this.sendRequest()
        .then(x => {
          console.warn('判斷是否是數組:', this.judgeArray(x))
        })
    }
  }
}

 

入口注入(index)

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

 

測試結果

 

 

npm發布

包名

@fines/factory-js

安裝

npm i @fines/factory-js

  當前測試版本

    0.0.5

注明

fines作為一個新的注冊的組織,這里將寫一些更美好的東西,以后所有能變得更美好的代碼都將發布到這個包下面(更重要一些包名已經無法使用,但是組織可以無限制)

 

github托管

地址

https://github.com/GerryIsWarrior/factory-js     感覺有參考意義可以點個star,內部正在使用踩坑中

Issues

https://github.com/GerryIsWarrior/factory-js/issues

demo地址

https://github.com/GerryIsWarrior/factory-js/tree/master/demo

PS:可直接clone下來 npm run start  直接跑起來測試

 

后記

以前在邏輯管理領域做過相關的摸索和思考,如下:

    1. 思考書寫更好可控的代碼
    2. 探索復雜前端業務的開發與設計

在之前的摸索基礎上,更深入的思考,才最終產出這個邏輯的解決方案,僅供大家參考,后面仍將持續完善該方案。

 

社區有人說,這不是你前端做的事,不是你的活,做這個干啥?聽完這句話,總感覺有點別扭。

在我看來,我們每個人都是一個架構師,不斷地在架構自己的代碼。不停的去認知世界的樣子,認知自我。我們都不是最完美的,有好也有壞。去發現自身痛點,對痛點進行分析,進行思考,找出最終的根源,然后再去思考如何去解決這個痛點,嘗試,摸索,失敗,階段性勝利,再繼續。就這樣一路走來,堅信終有收獲。共勉!

 

 

下期方向

 

組裝原子如何和原子共存,共建上層輸出邏輯?

因為有些通過原子邏輯組成的通用方法,也可以作為基礎原子繼續使用的,如何注入管理作為下一期課題研究。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM