OOP Mastery – Lý thuyết 03: Các loại Class, Interface trong Kotlin

Kotlin là một ngôn ngữ lập trình hiện đại, cung cấp nhiều loại Class, Interface để phục vụ việc lập trình của bạn. Bài viết này giúp bạn có cái nhìn cụ thể đến từng loại Class, Interface trong ngôn ngữ Kotlin để hiểu và có khả năng sử dụng chúng hiệu quả trong tương lai.

Kotlin Class

Đây là hình thái cơ sở của Kotlin để định nghĩa một lớp (Class). Cú pháp rất đơn giản

class MyClass {
  // Define các member của bạn
}

class Empty // khai báo một class không có gì bên trong

Constructor – Hàm tạo

Kotlin hỗ trợ nhiều cách tiếp cận của constructor cho một class

class MyClass(val intProp: Int) {
  fun samplePublicFunction() {
    println("Hello world!")
  }
}

class MyAnotherClass constructor(val intProp: Int) {
  fun anotherPublicFunction() {
    println("Hello world 2!")
  }
}

class MyClassWithMultipleConstructor {
    var stringProp: String = ""
    var intProp: Int = 0

    constructor(intProp: Int) {
        this.intProp = intProp
    }

    constructor(stringProp: String) {
        this.stringProp = stringProp
    }
}

Lưu ý: Default Access Modifier trong Kotlin là Public

Khởi tạo đối tượng – Object creation

Khác với C, C++ việc khởi tạo đối tượng phải thông quan Pointer (Con trỏ), Kotlin và 1 số ngôn ngữ hiện đại khác có cách khởi tạo đối tượng rất đơn giản.

val a = MyClass(1)
a.samplePublicFunction()
var b = MyAnotherClass(0)
b.anotherPublicFunction()

Destructor – Hàm hủy

Cơ chế cấp phát và quản lý memory của Kotlin sử dụng GC (Garbage Collector) khác biệt với ngôn ngữ C++ – mình sẽ không đi quá sâu để tránh lan man.

Chính vì sử dụng GC nên trong Kotlin ta không cần phải chủ động delete các đối tượng đã được tạo ra. GC sẽ tự phát hiện và delete các object đã được tạo ra khi chúng không còn được sử dụng nữa.

So sánh object

Koltin có hỗ trợ sẵn 2 toán tử so sánh là == và ===

  • == So sánh giá trị, so sánh content
  • === So sánh địa chỉ (reference) mà biến đó trỏ đến

Các Class trong kotlin nếu được khai báo bằng cú pháp mặc định class Myclass sẽ không bằng nhau trong các phép so sánh sử dụng toán tử == hoặc === cho dù các element bên trong chúng bằng nhau.

class MyClass(val name: String)
fun main() {
    val myClass1 = MyClass("Hello")
    val myClass2 = MyClass("Hello")
    println(myClass2 == myClass1) // false
    println(myClass2 === myClass1) // false
}

Làm cách nào để chúng bằng nhau trong trường hợp các properties bên trong bằng nhau?

Giải pháp là override lại hàm equals và hàm hashCode để dễ dàng so sánh.

class MyClass(val name: String) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || other !is MyClass) return false
        return name == other.name
    }

    override fun hashCode(): Int {
        return name.hashCode()
    }
}

fun main() {
    val myClass1 = MyClass("Hello")
    val myClass2 = MyClass("Hello")
    println(myClass2 == myClass1) // false
    println(myClass2 === myClass1) // false
    println(myClass1.hashCode() == myClass2.hashCode()) // false
}

Chính vì sự bất tiện này mà Kotlin đã sinh ra data class bạn sẽ được học ở phần tiếp theo.

Kotlin Interface

Interface – nhiều nguồn dịch là giao diện

Điều đặc biệt ở Interface trong Kotlin là nó có thể chứa được cả các function được định nghĩa sẵn, hoặc không, và chứa được cả các biến. Interface trong Kotlin đa dụng và tiện lợi hơn C++ rất nhiều.

interface MyUserInterface {
    var name: String
    var age: Int
    fun myFunction() {
        println("Hello From Interface User")
    }
}

class ProUser(
    override var name: String,
    override var age: Int
) : MyUserInterface {
    override fun myFunction() {
        println("Hello")
    }
}

fun main() {
    val user: MyUserInterface = ProUser("Pro", 20)
    println("User Info: ${user.name} ${user.age}")
    println("User Info: $user") // chỉ in ra địa chỉ của biến, không phải giá trị của nội dung
}

Trong ví dụ trên bạn không thể in ra thông tin của object user. Đó là lý do mà Kotlin tạo ra thêm Data Class.

Kotlin Data Class

Kotlin có khái niệm Data Class dùng để chỉ các Class các tác dụng chứa dữ liệu – only Data & no Logic.

Cách khai báo và sử dụng data class

data class MyUserDataClass(val name: String, var age: Int)
// Data class có thể chứa các biến thành viên là var hoặc val

fun main() {
    val myUserDataObject = MyUserDataClass("John", 25)
    val myUserDataObject2 = MyUserDataClass("John", 25)
    println("User Name: $myUserDataObject")
    println("myUserDataObject == myUserDataObject2: ${myUserDataObject == myUserDataObject2}")
    println("myUserDataObject.hashCode == myUserDataObject2.hashCode: ${myUserDataObject.hashCode() == myUserDataObject2.hashCode()}")
    println("myUserDataObject === myUserDataObject2: ${myUserDataObject === myUserDataObject2}")
}

Các đặc trưng của data class trong Kotlin

  • Hàm println sẽ in ra nội dung của object được tạo bởi data class thay vì địa chỉ của nó.
  • So sánh == giữa 2 object khác nhau được tạo bởi data class sẽ so sánh nội dung của mỗi object. Nếu chúng có chung giá trị sẽ được return TRUE
  • So sánh === giữa 2 object khác nhau được tạo bởi data class sẽ return FALSE. Vì chúng thuộc 2 vùng nhớ khác nhau.

Kotlin Enum Class

Enum là 1 khái niệm không mới trong ngôn ngữ lập trình hiện đại. Enum được dùng trong các logic để phân loại vì tính tiện dụng của nó.

Trong Kotlin, enum không có cách khai báo riêng biệt. Muốn khai báo một enum ta phải khai báo thông qua enum class

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}
enum class UserTier {
  FREE, PREMIUM
}

Enum class trong Kotlin rất đa dụng khi bạn có thể định nghĩa đính kèm thêm dữ liệu đi chung với từng loại enum.

enum class MessageState(val value: String) {
  FAILED("failed"), 
  SENDING("sending"), 
  RECEIVED("received"), 
  READ("read")
}

Lập trình viên có thể truy cập vào các giá trị của enum để thao tác tùy theo thiết kế của phần mềm.

enum class MyUserEnum(val role: String) {
    ADMIN("admin"),
    USER("user"),
    GUEST("guest")
}
fun main() {
    MyUserEnum.entries.forEach {
        println("string value: $it")
        println("role of enum:" + it.role)
        println("name of enum" + it.name)
        println("ordinal of enum: " + it.ordinal)
        println("---")
    }
}

Kotlin Sealed Class, Interface

Sealed Class, Interface trong Kotlin được tạo ra với mục đích ngăn chặn sự kế thừa từ Class, Interface sau khi code đã được compile.

Sealed Class and Interface in Kotlin will prevent the inheritance of Sub Classes after they compiled.

Đặt vấn đề: Lập trình viên của một thư viện viết bằng ngôn ngữ Kotlin không muốn các mã nguồn bên ngoài kế thừa các Class, Inteface của họ để mở rộng và phát triển. Lúc này sealed sẽ là giải pháp. Các mã nguồn có quyền sử dụng, nếu thư viện có lỗi cần phải chờ đợi một bản cập nhật mới từ Dev của thư viện đó, bạn không thể tự ý mở rộng hoặc kế thừa các Class, Interface của thư viện để phục vụ mục đích riêng.

Kotlin Object

Kotlin Object là giải pháp cho Singleton pattern trong ngôn ngữ Kotlin.

Trong ứng dụng của bạn sẽ có lúc cần những dữ liệu chỉ có 1 Instance duy nhất trên phạm vi toàn ứng dụng. Kotlin Object sẽ giúp làm việc đó trở nên đơn giản hơn.

object FilePathProvider {
  val cachePath: String = "file://cache"
  val diskPath: String = "file://disk"
}

Kotlin object cung cấp giải pháp Thread-safe cho Singleton pattern. Điều này có nghĩa là trong mọi trường hợp chương trình luôn tạo 1 và chỉ 1 Instance của 1 Kotlin object.