Phân biệt Abstract class và Interface trong Kotlin

1

Sự khác nhau giữa abstract class và interface là gì?”

Có lẽ đây là câu hỏi phổ biến nhất khi tuyển dụng. Mục đích của câu hỏi này là để kiểm tra mức độ hiểu biết về lập trình hướng đối tượng của ứng viên. Có thể bạn sẽ trả lời là:

  • Một Abstract class có thể có hàm với body.
  • Chúng ta có thể implement nhiều Interface cùng lúc được. Trong khi chỉ được extend một Class.

Những câu trả lời trên vừa chưa chính xác, lại chưa đủ. Ví dụ, Interface thực ra có thể có hàm với body, miễn là chúng không sử dụng từ khóa final.

Sự khác biệt đáng kể giữa abstract class và Interface là:

  • Interface không được có fields.
  • Chúng ta chỉ có thể extend từ một Class và được implement nhiều Interface.
  • Class có thêm hàm khởi tạo.

Để hiểu rõ hơn khi phân biệt Abstract class và Interface, mời bạn đọc tiếp!

Interface có thể có function

Interface trong Kotlin có thể có function với default body.

interface Animal {
    fun makeVoice() {
        print("<Animal voice>")
    }
}

class Fox: Animal

fun main() {
    val fox = Fox()
    fox.makeVoice() // <Animal voice>
}

Trong phần body, chúng ta có thể reference tới class thông qua từ khóa this

interface Animal {
    fun makeVoice() {
        print("<${this::class.simpleName} voice>")
    }
}

class Fox: Animal

fun main() {
    val fox = Fox()
    fox.makeVoice() // <Fox voice>
}

Những hàm trong interface không được đặt là final, và phải được override khi có class nào đó implement nó. Khi override, bạn vẫn có thể sử dụng logic trong default body bằng từ khóa super.

Đây là khác biệt lớn nhất giữa interface và abstract, khi mà abstract class có thể tạo các hàm final.

Ví dụ cách sử dụng từ khóa super:

interface Animal {
    fun makeVoice() {
        print("<${this::class.simpleName} voice>")
    }
}

class Fox: Animal {
    override fun makeVoice() {
        super.makeVoice()
        print(" (I prefer to stay quiet)")
    }
}

fun main() {
    val fox = Fox()
    fox.makeVoice() // <Fox voice> (I prefer to stay quiet)
}

Interfaces có thể có properties

Properties trong Kotlin đại diện cho các hàm setter/getter. Nếu trong Java, bạn muốn truy xuất vào một properties của một đối tượng, bạn phải thông qua các hàm setter/getter, đó gọi là tính đống gói của Java.

Nhưng với Kotlin thì lại khác, bạn truy xuất thằng vào properties luôn. Thực ra, gốc rễ bạn vẫn phải qua các hàm setter/getter để truy xuất vào properties, chỉ là nhà phát triển Kotlin họ làm hộ bạn mà thôi.

Ví dụ, bạn khai báo một class trong Kotlin:

class User {
    var name: String = ""
}

Và khi compile thì ra đoạn mã như sau:

public final class User {
   @NotNull
   private String name = "";

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }
}

Các properties có thể có sử dụng trong interface, miễn là chúng không có giá trị thực nào.

interface Animal {
    val name: String
}

class Fox: Animal {
    override val name: String = ""
}

Hoặc có giá trị mặc định

interface Animal {
    val name: String
    val type: String
    
    val fullName: String
        get() = "$name of type $type"
}

class Fox: Animal {
    override val type: String = "Tibetan sand fox"
    override val name: String = "Sox"
}

fun main() {
    val fox = Fox()
    print(fox.fullName) // Sox of type Tibetan sand fox
}

Interface có thể có state

Sử dụng state trong interface thực ra không phải là giải pháp tốt, nó chỉ là một thủ thuật khi bất khả kháng phải dùng tới. Mình nhắc tới nó ở đây chỉ để chứng mình rằng, Interface trong Kotlin có thể có state.

Nhưng chúng  ta có thể lưu trữ state ở đâu?

Có một số giải pháp nhưng tất cả đều không được tốt. Bởi vì trình dọn dẹp GC (Garbage Collector) không thể dọn dẹp chúng khi không sử dụng chúng.

Dưới đây là một giải pháp tốt nhất trong số các giải pháp xấu. Đó là lưu trữ trong companion object.

interface Animal {
    var name: String
        get() = names[this] ?: "Default name"
        set(value) { names[this] = value }

    var type: String
        get() = types[this] ?: "Default type"
        set(value) { types[this] = value }

    val fullName: String
        get() = "$name of type $type"

    companion object {
         private val names = mutableMapOf<Any, String>()
         private val types = mutableMapOf<Any, String>()
    }
}

class Fox: Animal
class Dog: Animal

fun main() {
    val fox = Fox()
    fox.name = "Sox"
    fox.type = "Tibetan sand fox"

    val dog = Dog()
    dog.name = "Billy"
    dog.type = "Dog"

    print(fox.fullName) // Sox of type Tibetan sand fox
    print(dog.fullName) // Billy of type Dog
}
Lưu ý: Giải pháp này chỉ mang tính chất minh họa và chứng minh rằng Interface có thể có State. Bạn không nên sử dụng nhé! Trừ khi bạn biết chắc mình đang làm gì? Hậu quả như thế nào cho ứng dụng?

Sự khác nhau giữa Abstract và Interface

Abstract class có mọi thứ mà Interface có. Ngoài ra, abstract class còn có thể field và hàm khởi tạo. Do đó, chúng ta có thể tạo và lưu trữ state trong abstract class:

abstract class Animal {
    var name: String = "Default name"
    var type: String = "Default type"
    
    val fullName: String
        get() = "$name of type $type"
}

class Fox: Animal()
class Dog: Animal()

fun main() {
    val fox = Fox()
    fox.name = "Sox"
    fox.type = "Tibetan sand fox"

    val dog = Dog()
    dog.name = "Billy"
    fox.type = "Dog"

    print(fox.fullName) // Sox of type Tibetan sand fox
    print(dog.fullName) // Billy of type Dog
}

Các function, properties với default body có thể thiết lập là final để tránh không override ở class con. Ngoài ra, chúng ta còn có thể tạo hàm khởi tạo, truyền giá trị cho abstract class.

abstract class Animal(
    var name: String = "Default name",
    var type: String = "Default type"
) {
    val fullName: String
        get() = "$name of type $type"
}

class Fox(name: String): Animal(name, "Fox")
class Dog(name: String): Animal(name, "Dog")

fun main() {
    val fox = Fox("Sox")
    val dog = Dog("Billy")
    print(fox.fullName) // Sox of type Fox
    print(dog.fullName) // Billy of type Dog
}
Nhắc lại: Cái này mình chỉ nhắc lại thôi, không thể tạo instance từ abstract class bằng toán tử NEW. Abstract class được thiết kế chỉ để cho các class khác extend nó mà thôi.

Như vậy, qua bài viết này bạn đã hiểu kỹ hơn và có thể Phân biệt Abstract class và Interface rồi đúng không? Mặc dù có thể các viết abstract class và Interface trong Kotlin có khác đôi chút so với Java nhưng về bản chất thì cũng giống nhau thôi.

Hi vọng bài viết này giúp ích được cho bạn, để lại bình luận bên dưới cho mình biết ý kiến của bạn nhé.

🔥 Đọc thêm về Kotlin:

Dịch vụ phát triển ứng dụng mobile giá rẻ - chất lượng

1
Bình luận. Cùng nhau thảo luận nhé!

avatar
  Theo dõi bình luận  
Mới nhất Cũ nhất Nhiều voted nhất
Thông báo
Mai Văn Viên
Guest
Mai Văn Viên

Bài viết khá chi tiết, cám ơn bạn