iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Software Development

AI 時代的 Android Engineer:30 天打造我的 AI 開發工作流系列 第 14 篇

Day 14 - 以前把 Timer 寫成 Singleton,現在的我還會這樣設計嗎?

  • 分享至 

  • xImage
  •  

前面幾天大部分都在研究怎麼跟 AI 一起寫 Code,但從今天開始,我想把重點慢慢拉回 Android 本身。

剛好這次拿來實驗的 Project 是我以前自己寫的,所以與其一直讓 AI 幫我找問題,我更想做另一件事:

回頭看以前自己做的 Android 設計,現在還會不會做出一樣的選擇?

第一個就從這次一直碰到的 Timer 開始。

目前 Pomodoro 和 Stopwatch 共用一個 TimeManager,而且它是一個 Singleton。

PomodoroFragment ─┐
                  ↓
              TimeManager
                  ↑
StopWatchFragment ┘

當初這樣做的理由其實很好理解。

Timer 不應該跟著某一個 Fragment 消失,Pomodoro 和 Stopwatch 又需要共用「現在到底是哪一種計時正在執行」的狀態,所以把計時集中到同一個地方管理很直覺。

但這次重新修改 Stopwatch 時,也剛好讓這個設計的代價全部跑出來。

Singleton 解決了什麼?

如果把計時直接放在 Fragment:

Fragment
↓
Timer
↓
更新畫面

最大的問題就是 Timer 太靠近 UI。

Fragment 可以因為切換畫面、重新建立等原因離開,但使用者認知中的「計時」不應該因此重新開始。

所以當時把 Timer 往 UI 外面移是合理的。

TimeManager 負責目前模式、計時狀態與經過時間,例如:

enum class Mode {
    IDLE,
    POMODORO,
    BREAK,
    STOPWATCH
}

enum class TimeState {
    IDLE,
    RUNNING,
    PAUSED
}

Fragment 只負責觀察狀態並更新畫面。

這樣至少解決了一件事:

Timer 的生命週期不再直接綁在某一個畫面上。

但 Singleton 也讓所有人看到同一份狀態

這次加入 Stopwatch 後,最明顯的問題就是 Pomodoro 和 Stopwatch 其實不是兩套獨立 Timer。

它們都在操作同一個 TimeManager。

所以:

Pomodoro Running
↓
TimeManager.mode = POMODORO

切換 Stopwatch
↓
還是同一個 TimeManager

這也是為什麼後來需要處理兩個 Timer 互斥,以及不同 Fragment 到底應不應該回應目前的時間。

Singleton 讓共享狀態變簡單,但同時也代表:

只要其中一個功能修改共享狀態,其他觀察者都有可能受到影響。

這次曾經出現 Stopwatch 計時後,Pomodoro 畫面也跟著顯示 Stopwatch 時間,就是很典型的例子。

問題不一定是 Singleton 本身,而是「共享了什麼」。

真正值得問的是:這些 State 應該綁在一起嗎?

如果只看到:

object TimeManager

然後直接討論「Singleton 好不好」,其實有點太快。

現在回頭看,我反而會先拆開裡面的狀態:

目前是哪種 Timer
目前 Running / Paused / Idle
開始時間
經過時間
目標時間

接著再問:

這些資料是不是本來就代表「目前 App 唯一正在進行的計時 Session」?

如果產品規則本來就是:

同一時間只能存在一個計時

那 Pomodoro 和 Stopwatch 共用一份目前計時狀態,其實不是奇怪的事情。

甚至可以說,互斥不是後來為了解決 Singleton 問題硬加的限制,而是 Domain 本身就需要表達的規則。

但如果需求是:

Pomodoro 可以跑
同時 Stopwatch 也可以跑

那一個全域 TimeManager 只保存:

mode = POMODORO

或:

mode = STOPWATCH

就根本無法完整表達這個需求。

所以 Architecture 合不合理,其實跟 Requirement 有直接關係。

如果今天重寫,我不會先問「Singleton 要不要拿掉」

以前看到 Singleton,很容易直接想到:

這是不是不好的設計?

但現在我比較想先問:

這個物件代表的是什麼?

如果它代表的是:

App 目前唯一的計時 Session

那它本來就需要被多個地方共享。

真正可以重新思考的是,它是不是一定要透過全域 Singleton 取得,以及依賴關係能不能更清楚。

例如現在:

Fragment
↓
直接取得 TimeManager

如果未來 Project 變大,也許可以變成由上層建立並提供同一個實例:

Application Scope
↓
Timer Manager
↓
ViewModel
↓
UI

共享狀態還是在,但「全域都可以直接拿到它」和「這個物件的生命週期是全域」其實是兩件不同的事情。

這也是我現在回頭看舊 Code 覺得很有意思的地方。

Singleton、生命週期、共享狀態其實不是同一題

以前很容易把它們混在一起:

我要讓 Timer 一直存在
↓
那就做 Singleton

但現在重新拆開看,其實至少有三個問題:

Timer 應該活多久?

哪些功能應該共享同一份 Timer 狀態?

誰負責建立並持有這個物件?

這三題的答案不一定都是「Singleton」。

Singleton 解決的是實例只有一份以及取得方式的問題,但它不會自動幫我決定狀態應該怎麼切、生命週期怎麼管理,也不代表所有使用者都應該直接依賴它。

回頭看自己的 Code,比直接問最佳實踐更有感

如果只是問:

Android 可以用 Singleton 管理 Timer 嗎?

很容易得到一堆「可以,但要注意……」的答案。

但拿自己以前真的寫過、而且這次又真的因為新增 Stopwatch 遇到共享狀態問題的 Code 回頭看,就會比較清楚:

當初為什麼這樣設計?

它解決了什麼?

這次新增需求後,哪個假設開始產生限制?

如果今天重寫,我會保留什麼,又會改什麼?

我覺得這比單純記:

Singleton 不好,不要用。

有用很多。

Architecture 很少是單純的「這樣寫對不對」,比較像是「這個設計當初解決了什麼,而現在的需求還適不適合它」。

接下來我想繼續用這種方式回頭看以前寫過的 Android Code,不是要證明以前寫錯了,而是重新把當時沒有想清楚的「為什麼」補回來。


上一篇
Day 13 - AI 寫完再叫它自己檢查,真的有用嗎?
下一篇
Day 15 - ViewModel可以保存狀態,所以什麼都放進ViewModel嗎?
系列文
AI 時代的 Android Engineer:30 天打造我的 AI 開發工作流 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言