Bỏ qua để đến nội dung

🎭 Object Declaration

🎯 Mục tiêu: Hiểu Object Declaration, Object Expression, và cách Kotlin hỗ trợ Singleton pattern ngay trong ngôn ngữ.


Singleton là design pattern đảm bảo một class chỉ có một instance duy nhất.

Trong Java, bạn phải tự implement:

// Java - verbose singleton
public class Database {
private static Database instance;
private Database() { }
public static synchronized Database getInstance() {
if (instance == null) {
instance = new Database();
}
return instance;
}
}

Kotlin có syntax riêng cho Singleton - object declaration:

// Kotlin - simple singleton
object Database {
fun connect() = println("Connected")
}
fun main() {
Database.connect() // Connected
// Database là singleton - chỉ có 1 instance
}

object MySingleton {
val property = "I'm a singleton property"
fun doSomething() {
println("Doing something")
}
}
fun main() {
println(MySingleton.property) // I'm a singleton property
MySingleton.doSomething() // Doing something
// MySingleton() ❌ Không thể tạo instance mới
}
object Logger {
private val logs = mutableListOf<String>()
fun log(level: String, message: String) {
val timestamp = java.time.LocalDateTime.now()
val entry = "[$timestamp][$level] $message"
logs.add(entry)
println(entry)
}
fun info(message: String) = log("INFO", message)
fun warn(message: String) = log("WARN", message)
fun error(message: String) = log("ERROR", message)
fun getLogs(): List<String> = logs.toList()
fun clearLogs() = logs.clear()
}
fun main() {
Logger.info("Application started")
Logger.warn("Low memory")
Logger.error("Connection failed")
println("Total logs: ${Logger.getLogs().size}") // 3
}
object DatabaseConnection {
private var isConnected = false
private lateinit var connectionString: String
fun configure(connectionString: String) {
this.connectionString = connectionString
}
fun connect(): Boolean {
if (!::connectionString.isInitialized) {
throw IllegalStateException("Database not configured")
}
if (!isConnected) {
println("Connecting to: $connectionString")
isConnected = true
} else {
println("Already connected")
}
return isConnected
}
fun disconnect() {
if (isConnected) {
println("Disconnecting...")
isConnected = false
}
}
fun query(sql: String): String {
require(isConnected) { "Not connected to database" }
return "Result for: $sql"
}
}
fun main() {
DatabaseConnection.configure("localhost:5432/mydb")
DatabaseConnection.connect()
println(DatabaseConnection.query("SELECT * FROM users"))
DatabaseConnection.disconnect()
}

Object có thể inherit class hoặc implement interface:

interface Comparator<T> {
fun compare(a: T, b: T): Int
}
// Object implementing interface
object StringLengthComparator : Comparator<String> {
override fun compare(a: String, b: String): Int {
return a.length - b.length
}
}
// Object extending class
open class Animal(val name: String) {
open fun speak() = println("...")
}
object Dog : Animal("Dog") {
override fun speak() = println("Woof!")
}
fun main() {
val words = listOf("apple", "hi", "banana")
val sorted = words.sortedWith(StringLengthComparator)
println(sorted) // [hi, apple, banana]
Dog.speak() // Woof!
}

Tạo object ẩn danh - thường dùng thay cho anonymous class trong Java:

interface OnClickListener {
fun onClick()
}
fun setClickListener(listener: OnClickListener) {
listener.onClick()
}
fun main() {
// Object expression - anonymous object
setClickListener(object : OnClickListener {
override fun onClick() {
println("Button clicked!")
}
})
}
interface Runnable {
fun run()
}
interface Cancelable {
fun cancel()
}
fun main() {
val task = object : Runnable, Cancelable {
override fun run() {
println("Task running...")
}
override fun cancel() {
println("Task cancelled")
}
}
task.run()
task.cancel()
}
fun createPoint(x: Int, y: Int) = object {
val xCoord = x
val yCoord = y
override fun toString() = "($xCoord, $yCoord)"
}
fun main() {
val point = createPoint(10, 20)
println(point) // (10, 20)
println("X: ${point.xCoord}") // X: 10
}

Feature Companion Object Object Declaration
Liên kết với class ✅ Có ❌ Không
Số lượng per class 1 N/A
Access syntax ClassName.member ObjectName.member
Có thể có tên ✅ Có N/A
Dùng cho Static-like members Standalone singletons
// Companion object - gắn với class
class User(val name: String) {
companion object {
fun create(name: String) = User(name)
}
}
// Object declaration - standalone
object UserRepository {
private val users = mutableListOf<User>()
fun add(user: User) = users.add(user)
fun getAll() = users.toList()
}
fun main() {
val user = User.create("Alice") // Companion object
UserRepository.add(user) // Object declaration
}

object ShoppingCart {
private val items = mutableListOf<CartItem>()
val totalItems: Int get() = items.size
val totalPrice: Double get() = items.sumOf { it.price * it.quantity }
fun addItem(item: CartItem) {
val existing = items.find { it.productId == item.productId }
if (existing != null) {
existing.quantity += item.quantity
} else {
items.add(item)
}
}
fun removeItem(productId: String) {
items.removeAll { it.productId == productId }
}
fun clear() = items.clear()
override fun toString(): String {
return items.joinToString("\n") { "- ${it.name}: ${it.quantity} x ${it.price}" }
}
}
data class CartItem(
val productId: String,
val name: String,
val price: Double,
var quantity: Int = 1
)
fun main() {
ShoppingCart.addItem(CartItem("1", "Laptop", 999.0, 1))
ShoppingCart.addItem(CartItem("2", "Mouse", 29.0, 2))
ShoppingCart.addItem(CartItem("1", "Laptop", 999.0, 1)) // Cộng quantity
println(ShoppingCart)
println("Total: $${ShoppingCart.totalPrice}")
// - Laptop: 2 x 999.0
// - Mouse: 2 x 29.0
// Total: $2056.0
}

Bài tập: Tạo ConfigManager singleton

// TODO: Tạo ConfigManager object với:
// - load(configMap: Map<String, Any>)
// - get(key: String): Any?
// - getString(key: String, default: String): String
// - getInt(key: String, default: Int): Int
// - getBoolean(key: String, default: Boolean): Boolean

Lời giải:

object ConfigManager {
private val config = mutableMapOf<String, Any>()
fun load(configMap: Map<String, Any>) {
config.clear()
config.putAll(configMap)
println("Loaded ${config.size} config values")
}
fun get(key: String): Any? = config[key]
fun getString(key: String, default: String = ""): String {
return config[key]?.toString() ?: default
}
fun getInt(key: String, default: Int = 0): Int {
return (config[key] as? Number)?.toInt() ?: default
}
fun getBoolean(key: String, default: Boolean = false): Boolean {
val value = config[key]
return when (value) {
is Boolean -> value
is String -> value.lowercase() == "true"
else -> default
}
}
fun keys(): Set<String> = config.keys.toSet()
}
fun main() {
ConfigManager.load(mapOf(
"app.name" to "MyApp",
"app.version" to 1,
"debug" to true,
"max.connections" to 100
))
println(ConfigManager.getString("app.name")) // MyApp
println(ConfigManager.getInt("max.connections")) // 100
println(ConfigManager.getBoolean("debug")) // true
println(ConfigManager.getString("missing", "N/A")) // N/A
}

// Application-wide singleton
object AppPreferences {
private lateinit var prefs: SharedPreferences
fun init(context: Context) {
prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
}
var isFirstLaunch: Boolean
get() = prefs.getBoolean("first_launch", true)
set(value) = prefs.edit().putBoolean("first_launch", value).apply()
var authToken: String?
get() = prefs.getString("auth_token", null)
set(value) = prefs.edit().putString("auth_token", value).apply()
}
// Network singleton with Retrofit
object ApiClient {
private const val BASE_URL = "https://api.example.com/"
val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
inline fun <reified T> createService(): T {
return retrofit.create(T::class.java)
}
}
// Click listener with object expression
button.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) {
// Handle click
}
})
// Simplified with lambda (SAM conversion)
button.setOnClickListener { /* Handle click */ }


Sau bài học này, bạn có thể:

  • Tạo singleton với object declaration
  • Hiểu object declaration được khởi tạo lazy và thread-safe
  • Tạo object kế thừa class hoặc implement interface
  • Sử dụng object expression cho anonymous objects
  • Phân biệt companion object và object declaration
  • Biết hạn chế của singleton trong testing

Tiếp theo: Companion Object