🎭 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 Pattern là gì?
Phần tiêu đề “💡 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 singletonpublic 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 singletonobject Database { fun connect() = println("Connected")}
fun main() { Database.connect() // Connected // Database là singleton - chỉ có 1 instance}📝 Object Declaration
Phần tiêu đề “📝 Object Declaration”Cú pháp cơ bản
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “🔧 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 interfaceobject StringLengthComparator : Comparator<String> { override fun compare(a: String, b: String): Int { return a.length - b.length }}
// Object extending classopen 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)
Phần tiêu đề “🎭 Object Expression (Anonymous Objects)”Tạo object ẩn danh - thường dùng thay cho anonymous class trong Java:
Implement interface
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “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}🏷️ Companion Object vs Object Declaration
Phần tiêu đề “🏷️ Companion Object vs Object Declaration”| 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 classclass User(val name: String) { companion object { fun create(name: String) = User(name) }}
// Object declaration - standaloneobject 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
Phần tiêu đề “📦 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
Phần tiêu đề “🛠️ 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): BooleanLờ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
Phần tiêu đề “📱 Trong Android”// Application-wide singletonobject 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 Retrofitobject 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 expressionbutton.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
Phần tiêu đề “⚠️ Lưu ý quan trọng”✅ Checklist - Tự kiểm tra
Phần tiêu đề “✅ Checklist - Tự kiểm tra”Sau bài học này, bạn có thể:
- Tạo singleton với
objectdeclaration - 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