1. 程式人生 > >邏輯管理:解決方案(一) - 關於前端邏輯管理的設計和實現

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

切入思考點

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

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

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

 

觀察程式碼

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

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

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

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

 

前端對業務做了什麼?

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

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

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

 

對邏輯的深入思考

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

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

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

 

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

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

 

理想設計架構圖

 

簡單摸索設計思路

原子邏輯:物件的基類,管理所有注入原子

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

對外介面:解析配置,呼叫原子和組合類管理、丟擲生產結果

 

思路圖如下:

 

基類設計程式碼

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

註明

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:可直接 npm run start  直接跑起來測試

 

後記

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

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

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

 

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

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

 

 

下期方向

 

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

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

 

&nb