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

🏗️ Class và Object

🎯 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ì?

Phần tiêu đề “💡 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
}
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

// 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]"
}

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

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
}

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

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

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
}

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): Boolean

Lờ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
}

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


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