“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!
Nội dung chính của bài viết
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 }
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 }
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:
Bài viết khá chi tiết, cám ơn bạn