Skip to Content
Kotlin📘 Ngôn ngữ Kotlin🎭 Object Declaration

Object Declaration và Singleton trong Kotlin

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


💡 Singleton Pattern là gì?

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 Declaration

Cú pháp cơ bản

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 }

Ví dụ thực tế: Logger

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 }

Database Connection Singleton

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 Declaration với Inheritance

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! }

🎭 Object Expression (Anonymous Objects)

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

Implement interface

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!") } }) }

Implement nhiều interfaces

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() }

Object expression với properties

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 }
⚠️

Scope của anonymous object properties:
Properties chỉ accessible khi return type là object expression (inferred type), không phải named type.

// ✅ Works - return type inferred as object private fun getPoint() = object { val x = 1; val y = 2 } // ❌ Properties not accessible - return type is Any fun getPointPublic(): Any = object { val x = 1; val y = 2 }

🏷️ Companion Object vs Object Declaration

FeatureCompanion ObjectObject Declaration
Liên kết với class✅ Có❌ Không
Số lượng per class1N/A
Access syntaxClassName.memberObjectName.member
Có thể có tên✅ CóN/A
Dùng choStatic-like membersStandalone 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 với State

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 }

🛠️ Thực hành

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 }

📱 Trong Android

// 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 */ }

⚠️ Lưu ý quan trọng

ℹ️

Initialization: Object declaration được khởi tạo lazy (lần đầu access) và thread-safe.

⚠️

Testing concern: Singleton khó mock/test. Trong production code, cân nhắc:

  • Dependency Injection thay vì global singletons
  • Interface abstraction để dễ mock
interface ILogger { fun log(message: String) } object DefaultLogger : ILogger { override fun log(message: String) = println(message) } // Testing class FakeLogger : ILogger { val logs = mutableListOf<String>() override fun log(message: String) = logs.add(message).let {} }

✅ Checklist - Tự kiểm tra

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

Last updated on