02.29 萬能樹Java工具類封裝(源碼)

0、學完本文你或許可以收穫

  • 感受一個樹工具從初始逐步優化完善的過程
  • 樹工具封裝的設計思考與實現思路
  • 最後收穫一款拿來即用的樹工具源代碼

對於前端樹組件有一定了解和使用過的同學可直接跳躍到第3章節開始。

1、樹長什麼樣 ?

前端的樹組件大多數情況下出現在後端的管理系統中,比如我們常見的菜單樹、機構樹、某某分類樹、樹表格等。大致像下方圖片所展示的這樣。

菜單樹

突破CRUD | 萬能樹Java工具類封裝(源碼)

機構樹

突破CRUD | 萬能樹Java工具類封裝(源碼)

org_tree.png

樹表格

突破CRUD | 萬能樹Java工具類封裝(源碼)

大致上來說,前端樹的展現形式就是上面3張圖所列的幾種形式。而這種前端樹組件的展現構成需要依賴於後端返回的數據格式。

2、數據格式

結合我自身使用過的前端樹組件來說,大致可以分為如下兩種。

列表形式

突破CRUD | 萬能樹Java工具類封裝(源碼)

列表形式

樹形結構

突破CRUD | 萬能樹Java工具類封裝(源碼)

樹形結構

本文所講的樹工具封裝主要是針對第二種數據格式樹形結構來說,因為第一種本身不需要特殊處理,也就不存在什麼封裝,就是簡單的列表查詢展示,與一般數據列表數據格式的區別是多了數據ID與父ID屬性提供給前端進行樹組件的構造。

而第二種是要在列表形式的數據格式上進行轉換,形成如上所示的樹形結構。但是,我們發現裡面沒有數據ID與父ID屬性,why ? 因為後端完成了數據層面樹結構的構造工作,前端樹組件再無需根據這兩個屬性進行樹結構的判斷構建,直接展示就OK,當然也不絕對,最終還得看前端的樹組件是否需要。

但一般都會保留這兩個屬性,因為除過樹組件自身的構造需求,業務處理上往往需要這兩個屬性,而後端樹工具要構造樹結構,那一定是需要數據ID與父ID的。

如果感覺上面說的麻煩你就記住一點,不管是列表結構還是樹形結構,始終保留數據ID與父ID兩個屬性就對了。

到這裡又有一個新問題了,上面說了列表形式無需封裝什麼可以直接使用,既然如此那用列表形式的結構就完了唄,為什麼寫個工具類搞個樹結構出來呢 ?

原因是,前端樹組件的實現方式非常多,不同樹插件或組件需要的數據格式可能不一樣,有的列表、樹形格式都支持,有的僅支持列表或樹形的一種,所以為了滿足不同前端樹的展示需求,提供樹形結構的構造工具是必要的。

3、話不多說,先實現個初版

從上面的內容我們瞭解了前端樹組件的渲染展現需要後端提供滿足需求的數據格式,那麼實際上也就決定了樹工具類的核心職責就是將一般的數據列表結構轉換為樹形結構,從而提供給前端使用。

解讀上面所述的核心職責,首先一般列表是什麼列表,此處我們假設為菜單列表,這就有了第一個類MenuEntity,緊接著是轉換,誰轉換成誰 ?數據列表轉換樹結構,樹結構本身那應該就是個類,我們暫且叫它 TreeNode,結合我們第一步假設的菜單列表,那實際上就是 List< MenuEntity > 轉換為 List < TreeNode > ,如此就得到了第二個類TreeNode,最後還剩轉換這個動作誰去做 ? 那就是我們今天的主角 TreeUtil 了。

好,至此,通過分析樹工具類的核心職責,我們分析得到了三個類。

  • MenuEntity
  • TreeNode
  • TreeUtil

OK,有了上面的內容那就來個簡單的實現。

樹節點類

突破CRUD | 萬能樹Java工具類封裝(源碼)

樹節點類

菜單類

突破CRUD | 萬能樹Java工具類封裝(源碼)

菜單類

樹工具類

突破CRUD | 萬能樹Java工具類封裝(源碼)

樹工具類

樹工具類實現的兩個關鍵點,第一,樹構建的開始位置也就是從哪裡開始構建,所以需要一個父ID參數來指定構建的起始位置,第二,構建到什麼時候結束,不做限制的的話,我們的樹是可以無限延伸的,所以此處innerBuild方法進行遞歸操作。

測試代碼

突破CRUD | 萬能樹Java工具類封裝(源碼)

測試代碼

收工,第一版簡單的樹工具就實現了。

4、迭代優化

1.0 這不是我的事

但是,通過測試代碼我們發現這個用起來不是太爽,要將菜單數據轉換為樹結構竟然需要我先把菜單列表轉換成樹結構的列表才能調用樹工具類的build方法,這裡的轉換操作僅僅是屬性的拷貝,並未完成樹狀結構的生成構建,但這是調用者需要關心的嗎 ?很顯然TreeNode集合創建生成這個過程應該是樹工具類應該做的事情。所以做了如下調整。

1 調整了build方法參數,將原有treeNodes 調整為 menuEntityList,意味著將上面說的treeNodes構建構成交給TreeUtil去做。

2 新增了Convert類,幷包含convert方法,該方法的職責是完成菜單實體到樹節點屬性的拷貝。

3 再次調整build方法參數,新增Convert轉換。

調整完成的結果,看下代碼。

樹工具

突破CRUD | 萬能樹Java工具類封裝(源碼)

樹工具1


突破CRUD | 萬能樹Java工具類封裝(源碼)

樹工具2

測試代碼

突破CRUD | 萬能樹Java工具類封裝(源碼)

測試代碼

比較1.0與初版的測試代碼,發現少了樹節點列表構建的過程,屬性拷貝的工作作為回調過程在轉換過程中進行處理。

2.0 僅支持造菜單樹哪夠

1.0優化完後,我們來了新的需求,有個機構樹也需要生成,此時的樹工具僅支持了菜單樹,所以我們進行改造,讓其支持其他任何對象的樹生成。

改造點主要是將的TreeUtil中的菜單實體轉換為泛型,限於篇幅,就貼個核心方法的代碼

突破CRUD | 萬能樹Java工具類封裝(源碼)

如此一來,我們就可以支持任意類型的樹構造。

3.0 哥們,你返回的屬性不夠用啊

前兩點比較容易想到,也比較容易實現,但這時候前端同學拋來了新的問題,哥們,你返回的樹節點屬性不夠用啊,你看我這界面。需要備註你沒返回來啊。

突破CRUD | 萬能樹Java工具類封裝(源碼)

好吧,這種情況確實沒考慮到。

要滿足上述需求,簡單做法就將remark屬性直接添加到 TreeNode 類中,Convert中賦下值,這不就滿足了,但想想又不對,今天這個前端夥計缺個remark,明天可能別的夥計又缺個其他屬性,全加到TreeNode中,TreeNode到底是樹節點還是業務實體,所以不能這麼搞。

這裡要處理成可擴展,同時滿足開閉原則,所以此處比較妥的處理方式是繼承,TreeNode屬性滿足不了的情況下,通過繼承擴展具體業務的樹節點來實現。

具體改造點如下

1 新增菜單實體擴展樹節點如下

突破CRUD | 萬能樹Java工具類封裝(源碼)

2 改造TreeUtil.build方法參數,新增TreeNode Class類型參數,如下

突破CRUD | 萬能樹Java工具類封裝(源碼)

測試代碼

突破CRUD | 萬能樹Java工具類封裝(源碼)

如此一來,不同業務場景下需要添加不同的屬性時,即可做到可擴展,且對現有代碼不造成任何影響和改動。

4.0 哥們,我的屬性名不叫code

完成了3.0版本,基本上大部分需求就都可以滿足了,但是這時候前端同學又拋來了新的問題,哥們,你返回的樹節點編號屬性是code,但我這邊的叫number,對應不上,我這邊調整的話影響比較大,你看後端返回的時候能不能處理下。

code屬性名肯定是不能調整的,因為其他模塊樹的節點編號都叫code。

那怎麼辦 ?其實也簡單,跟3.0版本一樣,在擴展的業務樹節點去加個屬性,這樣問題是解決了,但萬一出現所有treeNode的屬性名都跟前端需要的不對應這種極端情況,那意味著所有樹屬性都需要自行擴展定義,這種豈不是返回了沒什麼用的父TreeNode心中的所有屬性。序列化時倒是可以控制,為空的不進行序列化,但不是依賴序列化框架了麼。還有沒有其他辦法。

稍微整理下需求,就是樹節點屬性在返回前端時要能夠支持自定義屬性名

類屬性定義好就改不了了,怎麼自定義,除了新增類和改現有的屬性,還有什麼辦法呢 ?這時候我們應該想到map

具體怎麼做

1 首先,定義新的類TreeNodeMap,看名字就知道基於map實現

突破CRUD | 萬能樹Java工具類封裝(源碼)


突破CRUD | 萬能樹Java工具類封裝(源碼)


2 既然支持屬性名自定義,新增配置類TreeNodeConfig來完成這個事情,同時提供默認屬性名

突破CRUD | 萬能樹Java工具類封裝(源碼)

TreeNodeConfig

3 最後,改造TreeUtil.build 方法,基於2.0版本,只需將TreeNode替換成TreeNodeMap即可。

突破CRUD | 萬能樹Java工具類封裝(源碼)

TreeUtil.build

測試代碼

突破CRUD | 萬能樹Java工具類封裝(源碼)

經過上面的改造,我們實現了樹節點屬性的自定義,順便還實現了屬性可擴展,一舉兩得。

3、總結

目前這個程度可能仍有些場景無法滿足,但是對於大部分的問題場景基於3.0或4.0版本稍加改造應該都可以解決。剩下的就結合場景再酌情優化調整。

點贊、轉發、關注 三聯,私信回覆“萬能樹工具類”,即可獲取工具類源碼


分享到:


相關文章: