Class và Object trong Kotlin
🎯 Mục tiêu: Hiểu khái niệm OOP cơ bản, cách định nghĩa class, tạo object, và các tính năng đặc biệt của Kotlin như primary constructor.
💡 Lập trình hướng đối tượng (OOP) là gì?
Hãy tưởng tượng bạn đang xây một ngôi nhà:
- Class = Bản thiết kế nhà (blueprint)
- Object = Ngôi nhà thực tế được xây từ bản thiết kế
Một bản thiết kế có thể xây nhiều ngôi nhà giống nhau, tương tự một class có thể tạo nhiều objects.
// Class = Bản thiết kế
class Person(val name: String, var age: Int)
fun main() {
// Objects = Các thực thể được tạo từ class
val alice = Person("Alice", 25)
val bob = Person("Bob", 30)
println(alice.name) // Alice
println(bob.name) // Bob
}4 nguyên lý OOP cơ bản
| Nguyên lý | Mô tả | Ví dụ |
|---|---|---|
| Encapsulation | Đóng gói dữ liệu và phương thức | private var password |
| Inheritance | Kế thừa từ class cha | class Dog : Animal() |
| Polymorphism | Đa hình - nhiều hình thức | Override methods |
| Abstraction | Trừu tượng hóa | Interface, abstract class |
📝 Định nghĩa Class
Class đơn giản
// Class rỗng
class Empty
// Class với properties
class User {
var name: String = ""
var email: String = ""
}
fun main() {
val user = User()
user.name = "Alice"
user.email = "[email protected]"
}Primary Constructor - Cách Kotlin
Kotlin có syntax ngắn gọn và mạnh mẽ:
// Primary constructor với val/var tự động tạo properties
class User(val name: String, var email: String)
// Tương đương với Java (20+ dòng code!):
// class User {
// private final String name;
// private String email;
// public User(String name, String email) {
// this.name = name;
// this.email = email;
// }
// public String getName() { return name; }
// public String getEmail() { return email; }
// public void setEmail(String email) { this.email = email; }
// }val = readonly property (chỉ getter)
var = mutable property (getter + setter)
Không có val/var = chỉ là parameter, không tạo property
init block - Khởi tạo logic
class User(val name: String, val email: String) {
// init block chạy ngay sau khi object được tạo
init {
println("Creating user: $name")
require(name.isNotBlank()) { "Name cannot be blank" }
require(email.contains("@")) { "Invalid email format" }
}
// Có thể có nhiều init blocks
init {
println("User validated successfully!")
}
}
fun main() {
val user = User("Alice", "[email protected]")
// Output:
// Creating user: Alice
// User validated successfully!
}🔧 Secondary Constructor
Khi cần nhiều cách để tạo object:
class User(val name: String, val email: String) {
// Secondary constructor phải gọi primary constructor
constructor(name: String) : this(name, "$name@example.com") {
println("Created with default email")
}
constructor() : this("Guest") {
println("Created as guest")
}
}
fun main() {
val u1 = User("Alice", "[email protected]") // Primary
val u2 = User("Bob") // Secondary 1
val u3 = User() // Secondary 2
println(u2.email) // [email protected]
println(u3.name) // Guest
}Best Practice: Ưu tiên sử dụng default parameters thay vì secondary constructors:
class User(
val name: String = "Guest",
val email: String = "$name@example.com"
)📦 Properties
Computed Properties (Getter tùy chỉnh)
class Rectangle(val width: Int, val height: Int) {
// Computed property - tính toán mỗi lần truy cập
val area: Int
get() = width * height
val perimeter: Int
get() = 2 * (width + height)
val isSquare: Boolean
get() = width == height
}
fun main() {
val rect = Rectangle(10, 5)
println("Area: ${rect.area}") // 50
println("Perimeter: ${rect.perimeter}") // 30
println("Is square: ${rect.isSquare}") // false
}Custom Setter
class User(val id: Int) {
var name: String = ""
set(value) {
// 'field' là backing field
field = value.trim().replaceFirstChar { it.uppercase() }
}
var email: String = ""
set(value) {
require(value.contains("@")) { "Invalid email" }
field = value.lowercase()
}
}
fun main() {
val user = User(1)
user.name = " alice "
user.email = "[email protected]"
println(user.name) // Alice
println(user.email) // [email protected]
}Private Setter
class Counter {
var count: Int = 0
private set // Chỉ class này có thể thay đổi
fun increment() {
count++
}
}
fun main() {
val counter = Counter()
counter.increment()
println(counter.count) // 1
// counter.count = 100 // ❌ Lỗi! Không thể set từ bên ngoài
}Late-initialized Properties
class UserProfile {
// Sẽ được khởi tạo sau (ví dụ: sau khi load từ API)
lateinit var username: String
fun loadFromApi() {
// Giả sử load từ API
username = "loaded_user"
}
fun displayUsername() {
if (::username.isInitialized) {
println("Username: $username")
} else {
println("Username not loaded yet")
}
}
}lateinit chỉ dùng với:
var(không dùng vớival)- Non-primitive types (không dùng với
Int,Double,Boolean) - Nếu truy cập trước khi khởi tạo →
UninitializedPropertyAccessException
🔒 Visibility Modifiers
| Modifier | Trong class | Top-level |
|---|---|---|
public (mặc định) | Visible everywhere | Visible everywhere |
private | Chỉ trong class | Chỉ trong file |
protected | Class + subclasses | N/A (không dùng) |
internal | Module hiện tại | Module hiện tại |
class BankAccount(private val accountNumber: String) {
private var encryptedPassword: String = "" // Chỉ trong class
var balance: Double = 0.0
private set // Public get, private set
internal fun auditLog() { // Trong module
println("Audit: $accountNumber")
}
protected open fun calculateInterest(): Double { // Subclasses
return balance * 0.05
}
}🔑 Methods (Hàm trong Class)
class Calculator {
private var history = mutableListOf<String>()
fun add(a: Int, b: Int): Int {
val result = a + b
history.add("$a + $b = $result")
return result
}
fun subtract(a: Int, b: Int): Int {
val result = a - b
history.add("$a - $b = $result")
return result
}
fun printHistory() {
println("=== Lịch sử tính toán ===")
history.forEach { println(it) }
}
}
fun main() {
val calc = Calculator()
calc.add(10, 5) // 15
calc.subtract(10, 3) // 7
calc.printHistory()
// === Lịch sử tính toán ===
// 10 + 5 = 15
// 10 - 3 = 7
}🛠️ Thực hành
Bài tập 1: Tạo class Student
// TODO: Tạo class Student với:
// - id (readonly)
// - name (readonly)
// - scores (mutable list)
// - averageScore (computed property)
// - addScore(score: Double)
// - getGrade(): String (A/B/C/D/F)Lời giải:
class Student(val id: String, val name: String) {
private val scores = mutableListOf<Double>()
val averageScore: Double
get() = if (scores.isEmpty()) 0.0 else scores.average()
fun addScore(score: Double) {
require(score in 0.0..10.0) { "Score must be 0-10" }
scores.add(score)
}
fun getGrade(): String = when {
averageScore >= 9.0 -> "A"
averageScore >= 8.0 -> "B"
averageScore >= 7.0 -> "C"
averageScore >= 5.0 -> "D"
else -> "F"
}
override fun toString(): String {
return "Student($name, avg=${"%.2f".format(averageScore)}, grade=${getGrade()})"
}
}
fun main() {
val student = Student("SV001", "Nguyễn Văn A")
student.addScore(8.5)
student.addScore(9.0)
student.addScore(7.5)
println(student) // Student(Nguyễn Văn A, avg=8.33, grade=B)
}Bài tập 2: Tạo class BankAccount với validation
// TODO: Tạo class BankAccount với:
// - accountNumber (readonly)
// - ownerName (readonly)
// - balance (private set, khởi tạo = 0)
// - deposit(amount): validate amount > 0
// - withdraw(amount): Boolean - trả về true nếu thành công
// - transfer(to: BankAccount, amount): BooleanLời giải:
class BankAccount(
val accountNumber: String,
val ownerName: String
) {
var balance: Double = 0.0
private set
fun deposit(amount: Double) {
require(amount > 0) { "Amount must be positive" }
balance += amount
println("Deposited $${"%.2f".format(amount)}. New balance: $${"%.2f".format(balance)}")
}
fun withdraw(amount: Double): Boolean {
require(amount > 0) { "Amount must be positive" }
return if (amount <= balance) {
balance -= amount
println("Withdrew $${"%.2f".format(amount)}. New balance: $${"%.2f".format(balance)}")
true
} else {
println("Insufficient funds! Balance: $${"%.2f".format(balance)}")
false
}
}
fun transfer(to: BankAccount, amount: Double): Boolean {
if (withdraw(amount)) {
to.deposit(amount)
println("Transferred $${"%.2f".format(amount)} to ${to.ownerName}")
return true
}
return false
}
}
fun main() {
val alice = BankAccount("001", "Alice")
val bob = BankAccount("002", "Bob")
alice.deposit(1000.0)
alice.transfer(bob, 300.0)
println("\nFinal balances:")
println("Alice: $${alice.balance}") // 700.0
println("Bob: $${bob.balance}") // 300.0
}📱 Trong Android
// Model class cho User
class User(
val id: Long,
val name: String,
val email: String,
var isActive: Boolean = true
) {
val displayName: String
get() = if (name.isNotBlank()) name else "User #$id"
}
// ViewModel với state
class UserViewModel : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
fun loadUsers() {
viewModelScope.launch {
val result = repository.getUsers()
_users.value = result
}
}
}
// ViewBinding với custom class
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this)[UserViewModel::class.java]
observeUsers()
}
private fun observeUsers() {
viewModel.users.observe(this) { users ->
binding.userCount.text = "Users: ${users.size}"
}
}
}⚠️ Lưu ý quan trọng
Không dùng var trong constructor khi không cần thiết:
// ❌ Cho phép thay đổi từ bên ngoài - có thể gây bug
class User(var id: Int, var name: String)
// ✅ Id không nên thay đổi
class User(val id: Int, var name: String)Ưu tiên Data Class cho model classes:
// Thay vì viết thủ công
class User(val id: Int, val name: String) {
override fun equals(other: Any?): Boolean { ... }
override fun hashCode(): Int { ... }
override fun toString(): String { ... }
}
// ✅ Dùng data class
data class User(val id: Int, val name: String)
// Tự động có equals, hashCode, toString, copy!✅ Checklist - Tự kiểm tra
Sau bài học này, bạn có thể:
- Hiểu 4 nguyên lý OOP: Encapsulation, Inheritance, Polymorphism, Abstraction
- Phân biệt Class (blueprint) và Object (instance)
- Tạo class với primary constructor
- Sử dụng
val(readonly) vàvar(mutable) cho properties - Sử dụng init block để khởi tạo logic
- Tạo computed properties (custom getter)
- Sử dụng custom setter và private setter
- Hiểu visibility modifiers: public, private, protected, internal
- Biết khi nào dùng
lateinit
Tiếp theo: Property Accessors