在 Kotlin 中,類描述的是一種通用結構,可以多次實例化,也可以用多種方式實例化。但有時我們只需要單個實例,不多不少。單例模式能幫你更好地組織代碼,把相關的方法聚合在一起。
單例模式是什么?
單例模式是一種設計模式,保證一個類只有一個實例,并提供全局訪問點。這意味著你可以在代碼的任何地方獲取這個單例類的實例。打個比方,就像大家一起玩棋盤游戲,所有玩家都在同一個“棋盤”上進行操作,這個棋盤就相當于游戲的全局狀態。
單例的主要特征:
-
單例類只有一個實例。
-
單例類提供一個全局訪問點。
Kotlin 的對象聲明(object declaration)
單例模式非常有用,而 Kotlin 為單例提供了專門的語法結構:object
聲明。這是一種特殊的類聲明,使用關鍵字 object
創建單例。Kotlin 自動處理所有復雜步驟,你只需要用 object
聲明即可,無需手動實現單例模式。
object PlayingField {fun getAllPlayers(): Array<Player> {/* ... */}fun isPlayerInGame(player: Player): Boolean {/* ... */}}
解釋:
使用 object
聲明時,構造函數不可用,因為 Kotlin 自動完成。你可以通過 PlayingField
直接訪問這個單例實例,它在代碼任何地方調用都指向同一個對象。
示例:
fun startNewGameTurn() {val players = PlayingField.getAllPlayers()if (players.size < 2) {return println("The game cannot be continued without players")}for (player in players) {nextPlayerTurn(player)}
}fun nextPlayerTurn(player: Player) {if (!PlayingField.isPlayerInGame(player)) {return println("Current player lost. Next...")}/* 玩家行動 */
}
嵌套對象(Nested object)
有時候你想創建一個和另一個類相關聯的單例。例如,游戲中有 Player
類,代表不同的角色,這些角色有共享的屬性,比如默認速度。你如何保存這些共享信息呢?
你可以簡單地創建一個單例對象:
object PlayerProperties {/* 默認速度,每回合移動7格 */val defaultSpeed = 7fun calcMovePenalty(cell: Int): Int {/* 計算移動速度懲罰 */}
}
但如果項目里有許多類似的單例,代碼會變得難讀。更好的做法是將單例嵌套到相關類中。
class Player(val id: Int) {object Properties {val defaultSpeed = 7fun calcMovePenalty(cell: Int): Int {/* 計算移動懲罰 */}}
}/* 輸出 7 */
println(Player.Properties.defaultSpeed)
Properties
對象作用域是 Player
,只能通過 Player.Properties
訪問。這種方式讓單例和類有明確的關聯。
你還可以在外部類中使用嵌套對象的屬性:
class Player(val id: Int) {object Properties {val defaultSpeed = 7}val superSpeed = Properties.defaultSpeed * 2 // 14
}
但反過來是不行的——嵌套對象中不能訪問外部類的實例成員:
class Player(val id: Int) { val speed = 7object Properties {val defaultSpeed = speed // 錯誤,不能訪問外部類實例屬性}
}
這和其他語言的 static
類似,Kotlin 沒有默認的靜態成員,但可以用嵌套對象來達到類似效果。
編譯時常量(Compile-time constants)
如果某個只讀屬性永遠不會改變,我們稱它為常量。可以使用 const
關鍵字聲明編譯時常量:
object Languages {const val FAVORITE_LANGUAGE = "Kotlin"
}
要求:
-
必須是基本類型或 String。
-
不能有自定義 getter。
-
命名用全大寫加下劃線(SCREAMING_SNAKE_CASE)。
比如游戲里的默認速度可以寫成:
object Properties {const val DEFAULT_SPEED = 7
}
訪問:
println(Properties.DEFAULT_SPEED) // 輸出 7
為什么不都用頂層常量?因為大量無關聯的頂層常量會讓代碼混亂,影響閱讀。最好把和某個對象相關的常量放到對應的對象里。
對象與嵌套對象的擴展
你可以在一個類里聲明多個對象,比如:
class Player(val id: Int) {object Properties {val defaultSpeed = 7fun calcMovePenalty(cell: Int): Int { /* ... */ }}object Factory {fun create(playerId: Int): Player {return Player(playerId)}}
}println(Player.Properties.defaultSpeed) // 7
println(Player.Factory.create(13).id) // 13
這里的 Factory
是工廠模式,用來創建 Player
實例。你也可以在一個對象內部聲明多個對象,用來組織單例數據:
object Game {object Properties {val maxPlayersCount = 13val maxGameDurationInSec = 2400}object Info {val name = "My super game"}
}
數據對象(Data object)
普通的對象聲明打印會顯示類名和哈希碼:
object MyObjectfun main() {println(MyObject) // MyObject@1f32e575
}
如果用 data
修飾單例對象,會生成更友好的方法:
data object MySingletonfun main() {println(MySingleton) // MySingleton
}
注意,data object
不是數據類,不能復制(沒有 copy()
方法),也沒有組件函數,因為單例不允許多實例。
總結
Kotlin 中,object
聲明是創建單例的標準方式,也可以用嵌套對象關聯類本身,而非類的實例。合理使用它們可以讓代碼結構更清晰,提升可讀性和可維護性。