2020 年初我在一個專案中,要做一件麻煩的事情。
需求是
要把原本一頁式的頁面 (Single UINavigationController) 換成 TabBarController 的樣式。
看似平凡無奇的描述中,因為 Legacy code 和 跨區域開發者 的組成,產生了不少惡魔。但是也因此,讓我對於 Generic 有了新解。
表象問題
這種「頁面架構」的需求變換,通常伴隨著一個最麻煩的問題
頁面跳轉 (超連結) 的邏輯需要大幅度變動
比如說一個需求是:
當意圖 A 發生時,要離開目前的頁面,從一般 Account ViewController 的進入點顯示 Account ViewController。
(意圖 A 可能會是由一個 Universal Link 產生,或是某個頁面的動作。)
在 Single UINavigationController 時,只需要 popToRoot 後,接著 push Account ViewController。
但是在 TabBarController 時,因為 Account 也自成一個 Tab 了,則會直接切換過去。
如果是一個有結構性的設計,這個程度的轉換應該還好。
但可以理解過去沒有這樣的需求,所以通常會是直接使用:
把所有的邏輯,都寫在 UINavigationController 的 sub-class 下。
包含 1. 意圖的解析, 2. 解析後的動作實踐
因此會看到這樣的程式碼:
1 | class ProjectNavigationController: UINavigationController { |
到目前為止,如果轉換成 protocol
,然後將實作換成 TabBarController
都還算簡單。
但因為多人維護的關係,這件事有了變化…
當有一個新的需求:
當收到 Deep Link 時,要在 navigationController 顯示一個 Login Flow
Login Flow 由另一個 Manager 管理很正常,通常只要告訴他要顯示在哪個 ViewController 之上就可以了
1 | class ProjectNavigationController: UINavigationController { |
由於這個需求看起來很簡單,加上 ProjectNavigationController
也將 UIViewController
的屬性暴露在外面。所以被這寫出來的機會就很大。
因此,就無法直接將 ProjectNavigationController
的實體物件,換成虛擬的 protocol 了。
為何以及如何使用 Generic
因此無法單純改成介面來使用。如果要修改這類的使用方式,勢必對更多關聯的物件產生影響,不僅僅是程式碼的改動,也要修改到其他開發者的開發思維。
因此可以借用 Generic 來幫忙。
1 | protocol ProjectNavigationControlling: UIViewController { |
這樣改寫,會發現 DeepLinkManager
基本上沒有什麼變動。因此如果要改成 TabBarController 的形式(也正好 TabBarController 是繼承 UIViewController)也需要只考慮實作 ProjectNavigationControlling
的問題就好了,例如:
1 | class ProjectNavigationController: TabBarController, ProjectNavigationControlling { |
這樣對短期的轉換風險會比較少,而且可以分成「重構」和「轉接 TabBarController」兩個階段。但仍然會有被直接使用 UIViewController 而增加相依性的風險。
通常公司有成本考量,所以先以低風險為主應該會比較好。
不改變其他開發者的開發思維,通常他們也比較能接受。
小結
(這篇拖了一年多才寫完XD)
這一年多的時間裡,我又寫了不少的 Generic。
回過頭來看,覺得當時做的決策還是相當有用。
目前這個 Legacy 架構還是躺在那裡好好的,哈!
抽象化在程式中,是蠻重要的一個課題。
不要拘泥於實作物件能做什麼,而是原本的功能、需求要的是什麼。