Chương trình đào tạo về Kotlin dành cho lập trình viên 4: Lập trình hướng đối tượng

Lớp học lập trình này nằm trong khoá học Chương trình đào tạo về Kotlin dành cho lập trình viên. Bạn sẽ nhận được nhiều giá trị nhất qua khoá học này nếu thực hiện các lớp học lập trình theo trình tự. Tuỳ thuộc vào kiến thức của mình, bạn có thể lướt qua một số phần. Khoá học này hướng tới những lập trình viên biết một ngôn ngữ hướng đối tượng và muốn học Kotlin.

Giới thiệu

Trong lớp học lập trình này, bạn sẽ tạo một chương trình Kotlin và tìm hiểu về các lớp cũng như đối tượng trong Kotlin. Bạn sẽ thấy quen thuộc với phần lớn nội dung này nếu biết một ngôn ngữ hướng đối tượng khác. Tuy nhiên, Kotlin có một số điểm khác biệt quan trọng để giảm lượng mã bạn cần viết. Bạn cũng sẽ tìm hiểu về các lớp trừu tượng và việc uỷ quyền giao diện.

Thay vì tạo một ứng dụng mẫu duy nhất, các bài học trong khoá học này được thiết kế để giúp bạn nâng cao kiến thức, nhưng các bài học này có tính độc lập tương đối với nhau để bạn có thể lướt qua những phần mà bạn đã quen thuộc. Để liên kết các ví dụ này với nhau, nhiều ví dụ sử dụng chủ đề hồ cá. Nếu bạn muốn xem toàn bộ câu chuyện về bể cá, hãy tham khảo khoá học Kotlin Bootcamp dành cho lập trình viên của Udacity.

Kiến thức bạn cần có

  • Kiến thức cơ bản về Kotlin, bao gồm các loại, toán tử và vòng lặp
  • Cú pháp hàm của Kotlin
  • Kiến thức cơ bản về lập trình hướng đối tượng
  • Kiến thức cơ bản về một IDE như IntelliJ IDEA hoặc Android Studio

Kiến thức bạn sẽ học được

  • Cách tạo lớp và truy cập vào các thuộc tính trong Kotlin
  • Cách tạo và sử dụng hàm khởi tạo lớp trong Kotlin
  • Cách tạo một lớp con và cách thức hoạt động của tính kế thừa
  • Giới thiệu về các lớp, giao diện trừu tượng và việc uỷ quyền giao diện
  • Cách tạo và sử dụng các lớp dữ liệu
  • Cách sử dụng singleton, enum và lớp kín

Bạn sẽ thực hiện

  • Tạo một lớp có các thuộc tính
  • Tạo hàm khởi tạo cho một lớp
  • Tạo một lớp con
  • Xem xét các ví dụ về lớp và giao diện trừu tượng
  • Tạo một lớp dữ liệu đơn giản
  • Tìm hiểu về singleton, enum và lớp kín

Bạn cần nắm rõ các thuật ngữ lập trình sau:

  • Lớp là sơ đồ thiết kế của các đối tượng. Ví dụ: lớp Aquarium là bản thiết kế để tạo một đối tượng hồ cá.
  • Đối tượng là các phiên bản của lớp; đối tượng hồ cá là một Aquarium thực tế.
  • Thuộc tính là đặc điểm của các lớp, chẳng hạn như chiều dài, chiều rộng và chiều cao của một Aquarium.
  • Phương thức, còn được gọi là hàm thành phần, là chức năng của lớp. Phương thức là những gì bạn có thể "làm" với đối tượng. Ví dụ: bạn có thể fillWithWater() một đối tượng Aquarium.
  • Giao diện là một quy cách mà một lớp có thể triển khai. Ví dụ: việc vệ sinh là hoạt động thường thấy đối với các đối tượng khác ngoài bể cá và việc vệ sinh thường diễn ra theo cách tương tự đối với các đối tượng khác nhau. Vì vậy, bạn có thể có một giao diện tên là Clean xác định một phương thức clean(). Lớp Aquarium có thể triển khai giao diện Clean để làm sạch bể cá bằng miếng bọt biển mềm.
  • Gói là một cách để nhóm mã có liên quan nhằm sắp xếp mã đó hoặc tạo một thư viện mã. Sau khi tạo một gói, bạn có thể nhập nội dung của gói đó vào một tệp khác và sử dụng lại mã cũng như các lớp trong gói đó.

Trong nhiệm vụ này, bạn sẽ tạo một gói mới và một lớp có một số thuộc tính và phương thức.

Bước 1: Tạo một gói

Các gói có thể giúp bạn sắp xếp mã một cách khoa học.

  1. Trong ngăn Project (Dự án), bên dưới dự án Hello Kotlin, hãy nhấp chuột phải vào thư mục src.
  2. Chọn New (Mới) > Package (Gói) rồi gọi gói đó là example.myapp.

Bước 2: Tạo một lớp có các thuộc tính

Các lớp được xác định bằng từ khoá class và theo quy ước, tên lớp bắt đầu bằng một chữ cái viết hoa.

  1. Nhấp chuột phải vào gói example.myapp.
  2. Chọn New > Kotlin File / Class (Mới > Tệp/Lớp Kotlin).
  3. Trong phần Kind (Loại), hãy chọn Class (Lớp) rồi đặt tên cho lớp là Aquarium. IntelliJ IDEA sẽ thêm tên gói vào tệp và tạo một lớp Aquarium trống cho bạn.
  4. Bên trong lớp Aquarium, hãy xác định và khởi chạy các thuộc tính var cho chiều rộng, chiều cao và chiều dài (tính bằng cm). Khởi chạy các thuộc tính bằng các giá trị mặc định.
package example.myapp

class Aquarium {
    var width: Int = 20
    var height: Int = 40
    var length: Int = 100
}

Trong nội bộ, Kotlin sẽ tự động tạo các phương thức getter và setter cho những thuộc tính mà bạn đã xác định trong lớp Aquarium, nhờ đó bạn có thể truy cập trực tiếp vào các thuộc tính, chẳng hạn như myAquarium.length.

Bước 3: Tạo một hàm main()

Tạo một tệp mới có tên là main.kt để lưu giữ hàm main().

  1. Trong ngăn Project (Dự án) ở bên trái, hãy nhấp chuột phải vào gói example.myapp.
  2. Chọn New > Kotlin File / Class (Mới > Tệp/Lớp Kotlin).
  3. Trong trình đơn thả xuống Loại, hãy giữ lựa chọn là Tệp và đặt tên cho tệp là main.kt. IntelliJ IDEA có tên gói nhưng không có định nghĩa lớp cho tệp.
  4. Xác định hàm buildAquarium() và tạo một thực thể của Aquarium bên trong. Để tạo một phiên bản, hãy tham chiếu đến lớp như thể đó là một hàm, Aquarium(). Thao tác này sẽ gọi hàm khởi tạo của lớp và tạo một thực thể của lớp Aquarium, tương tự như việc sử dụng new trong các ngôn ngữ khác.
  5. Xác định hàm main() và gọi buildAquarium().
package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium()
}

fun main() {
    buildAquarium()
}

Bước 4: Thêm một phương thức

  1. Trong lớp Aquarium, hãy thêm một phương thức để in các thuộc tính kích thước của hồ cá.
    fun printSize() {
        println("Width: $width cm " +
                "Length: $length cm " +
                "Height: $height cm ")
    }
  1. Trong main.kt, trong buildAquarium(), hãy gọi phương thức printSize() trên myAquarium.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
}
  1. Chạy chương trình bằng cách nhấp vào hình tam giác màu xanh lục bên cạnh hàm main(). Quan sát kết quả.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
  1. Trong buildAquarium(), hãy thêm mã để đặt chiều cao thành 60 và in các thuộc tính kích thước đã thay đổi.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
    myAquarium.height = 60
    myAquarium.printSize()
}
  1. Chạy chương trình rồi quan sát kết quả.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 100 cm Height: 60 cm 

Trong bài này, bạn sẽ tạo một hàm khởi tạo cho lớp và tiếp tục làm việc với các thuộc tính.

Bước 1: Tạo một hàm khởi tạo

Trong bước này, bạn sẽ thêm một hàm khởi tạo vào lớp Aquarium mà bạn đã tạo trong nhiệm vụ đầu tiên. Trong ví dụ trước đó, mọi thực thể của Aquarium đều được tạo bằng các phương diện giống nhau. Bạn có thể thay đổi kích thước sau khi tạo bằng cách thiết lập các thuộc tính, nhưng sẽ đơn giản hơn nếu bạn tạo kích thước chính xác ngay từ đầu.

Trong một số ngôn ngữ lập trình, hàm khởi tạo được xác định bằng cách tạo một phương thức trong lớp có cùng tên với lớp. Trong Kotlin, bạn xác định hàm khởi tạo ngay trong chính phần khai báo lớp, chỉ định các tham số bên trong dấu ngoặc đơn như thể lớp đó là một phương thức. Giống như các hàm trong Kotlin, những tham số đó có thể bao gồm các giá trị mặc định.

  1. Trong lớp Aquarium mà bạn đã tạo trước đó, hãy thay đổi định nghĩa lớp để thêm 3 tham số hàm khởi tạo có giá trị mặc định cho length, widthheight, đồng thời chỉ định các tham số này cho các thuộc tính tương ứng.
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
   // Dimensions in cm
   var length: Int = length
   var width: Int = width
   var height: Int = height
...
}
  1. Cách Kotlin nhỏ gọn hơn là xác định các thuộc tính trực tiếp bằng hàm khởi tạo, sử dụng var hoặc val, đồng thời Kotlin cũng tự động tạo các phương thức getter và setter. Sau đó, bạn có thể xoá các định nghĩa thuộc tính trong phần nội dung của lớp.
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
  1. Khi tạo một đối tượng Aquarium bằng hàm khởi tạo đó, bạn có thể không chỉ định đối số và nhận các giá trị mặc định, hoặc chỉ định một số đối số hoặc chỉ định tất cả các đối số và tạo một Aquarium có kích thước hoàn toàn tuỳ chỉnh. Trong hàm buildAquarium(), hãy thử các cách tạo đối tượng Aquarium bằng cách sử dụng các tham số được đặt tên.
fun buildAquarium() {
    val aquarium1 = Aquarium()
    aquarium1.printSize()
    // default height and length
    val aquarium2 = Aquarium(width = 25)
    aquarium2.printSize()
    // default width
    val aquarium3 = Aquarium(height = 35, length = 110)
    aquarium3.printSize()
    // everything custom
    val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
    aquarium4.printSize()
}
  1. Chạy chương trình và quan sát kết quả.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 25 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 110 cm Height: 35 cm 
Width: 25 cm Length: 110 cm Height: 35 cm 

Lưu ý rằng bạn không cần phải nạp chồng hàm khởi tạo và viết một phiên bản khác cho từng trường hợp này (cộng thêm một vài trường hợp nữa cho các tổ hợp khác). Kotlin tạo những gì cần thiết từ các giá trị mặc định và tham số được đặt tên.

Bước 2: Thêm các khối khởi tạo

Các hàm khởi tạo ví dụ ở trên chỉ khai báo các thuộc tính và gán giá trị của một biểu thức cho các thuộc tính đó. Nếu hàm dựng của bạn cần thêm mã khởi tạo, thì mã đó có thể được đặt trong một hoặc nhiều khối init. Trong bước này, bạn sẽ thêm một số khối init vào lớp Aquarium.

  1. Trong lớp Aquarium, hãy thêm một khối init để in rằng đối tượng đang khởi tạo và một khối thứ hai để in thể tích tính bằng lít.
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
    init {
        println("aquarium initializing")
    }
    init {
        // 1 liter = 1000 cm^3
        println("Volume: ${width * length * height / 1000} l")
    }
}
  1. Chạy chương trình và quan sát kết quả.
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm 
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm 

Xin lưu ý rằng các khối init được thực thi theo thứ tự xuất hiện trong định nghĩa lớp và tất cả các khối này đều được thực thi khi hàm khởi tạo được gọi.

Bước 3: Tìm hiểu về hàm dựng phụ

Trong bước này, bạn sẽ tìm hiểu về hàm khởi tạo phụ và thêm một hàm khởi tạo phụ vào lớp. Ngoài hàm khởi tạo chính (có thể có một hoặc nhiều khối init), một lớp Kotlin cũng có thể có một hoặc nhiều hàm khởi tạo phụ để cho phép nạp chồng hàm khởi tạo, tức là các hàm khởi tạo có đối số khác nhau.

  1. Trong lớp Aquarium, hãy thêm một hàm khởi tạo phụ lấy số lượng cá làm đối số, bằng cách dùng từ khoá constructor. Tạo một thuộc tính val cho bể cá để tính thể tích của bể cá theo lít dựa trên số lượng cá. Giả sử mỗi con cá cần 2 lít (2.000 cm^3) nước, cộng thêm một chút không gian để nước không bị tràn.
constructor(numberOfFish: Int) : this() {
    // 2,000 cm^3 per fish + extra room so water doesn't spill
    val tank = numberOfFish * 2000 * 1.1
}
  1. Trong hàm dựng phụ, hãy giữ nguyên chiều dài và chiều rộng (được đặt trong hàm dựng chính) và tính chiều cao cần thiết để bình chứa có thể tích đã cho.
    // calculate the height needed
    height = (tank / (length * width)).toInt()
  1. Trong hàm buildAquarium(), hãy thêm một lệnh gọi để tạo Aquarium bằng hàm khởi tạo phụ mới. In kích thước và thể tích.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
  1. Chạy chương trình rồi quan sát kết quả.
⇒ aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

Xin lưu ý rằng âm lượng được in hai lần, một lần bằng khối init trong hàm dựng chính trước khi hàm dựng phụ được thực thi và một lần bằng mã trong buildAquarium().

Bạn cũng có thể thêm từ khoá constructor vào hàm khởi tạo chính, nhưng không cần thiết trong hầu hết các trường hợp.

Bước 4: Thêm một phương thức truy xuất thuộc tính mới

Trong bước này, bạn sẽ thêm một phương thức truy xuất thuộc tính rõ ràng. Kotlin tự động xác định getter và setter khi bạn xác định các thuộc tính, nhưng đôi khi bạn cần điều chỉnh hoặc tính toán giá trị cho một thuộc tính. Ví dụ: ở trên, bạn đã in âm lượng của Aquarium. Bạn có thể cung cấp âm lượng dưới dạng một thuộc tính bằng cách xác định một biến và một getter cho biến đó. Vì volume cần được tính toán, nên phương thức getter cần trả về giá trị đã tính. Bạn có thể thực hiện việc này bằng một hàm một dòng.

  1. Trong lớp Aquarium, hãy xác định một thuộc tính Int có tên là volume và xác định một phương thức get() để tính toán âm lượng ở dòng tiếp theo.
val volume: Int
    get() = width * height * length / 1000  // 1000 cm^3 = 1 l
  1. Xoá khối init in âm lượng.
  2. Xoá mã trong buildAquarium() in âm lượng.
  3. Trong phương thức printSize(), hãy thêm một dòng để in âm lượng.
fun printSize() {
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm "
    )
    // 1 l = 1000 cm^3
    println("Volume: $volume l")
}
  1. Chạy chương trình rồi quan sát kết quả.
⇒ aquarium initializing
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

Kích thước và thể tích vẫn như cũ, nhưng thể tích chỉ được in một lần sau khi đối tượng được khởi tạo đầy đủ bởi cả hàm khởi tạo chính và hàm khởi tạo phụ.

Bước 5: Thêm một phương thức thiết lập thuộc tính

Trong bước này, bạn sẽ tạo một bộ thiết lập thuộc tính mới cho âm lượng.

  1. Trong lớp Aquarium, hãy thay đổi volume thành var để có thể đặt nhiều lần.
  2. Thêm một setter cho thuộc tính volume bằng cách thêm phương thức set() bên dưới getter. Phương thức này sẽ tính toán lại chiều cao dựa trên lượng nước được cung cấp. Theo quy ước, tên của tham số setter là value, nhưng bạn có thể thay đổi tên này nếu muốn.
var volume: Int
    get() = width * height * length / 1000
    set(value) {
        height = (value * 1000) / (width * length)
    }
  1. Trong buildAquarium(), hãy thêm mã để đặt thể tích của bể cá thành 70 lít. In kích thước mới.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    aquarium6.volume = 70
    aquarium6.printSize()
}
  1. Chạy lại chương trình và quan sát chiều cao cũng như thể tích đã thay đổi.
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm 
Volume: 70 l

Cho đến nay, chưa có công cụ sửa đổi chế độ hiển thị nào, chẳng hạn như public hoặc private, trong mã. Đó là vì theo mặc định, mọi thứ trong Kotlin đều ở chế độ công khai, tức là mọi thứ đều có thể truy cập ở mọi nơi, bao gồm cả các lớp, phương thức, thuộc tính và biến thành viên.

Trong Kotlin, các lớp, đối tượng, giao diện, hàm dựng, hàm, thuộc tính và phương thức setter có thể có các từ khoá xác định mức độ hiển thị:

  • public nghĩa là có thể nhìn thấy bên ngoài lớp. Mọi thứ đều ở chế độ công khai theo mặc định, bao gồm cả các biến và phương thức của lớp.
  • internal nghĩa là chỉ hiển thị trong mô-đun đó. Mô-đun là một tập hợp các tệp Kotlin được biên dịch cùng nhau, chẳng hạn như một thư viện hoặc ứng dụng.
  • private nghĩa là chỉ hiển thị trong lớp đó (hoặc tệp nguồn nếu bạn đang làm việc với các hàm).
  • protected giống như private, nhưng cũng sẽ hiển thị với bất kỳ lớp con nào.

Hãy xem phần Chỉ định truy cập trong tài liệu về Kotlin để biết thêm thông tin.

Biến thành phần

Theo mặc định, các thuộc tính trong một lớp hoặc các biến thành phần là public. Nếu bạn xác định chúng bằng var, chúng sẽ có thể thay đổi, tức là có thể đọc và ghi. Nếu bạn xác định các thuộc tính này bằng val, thì chúng sẽ chỉ có thể đọc sau khi khởi tạo.

Nếu muốn một thuộc tính mà mã của bạn có thể đọc hoặc ghi, nhưng mã bên ngoài chỉ có thể đọc, bạn có thể giữ thuộc tính và phương thức getter ở chế độ công khai, đồng thời khai báo phương thức setter ở chế độ riêng tư, như minh hoạ dưới đây.

var volume: Int
    get() = width * height * length / 1000
    private set(value) {
        height = (value * 1000) / (width * length)
    }

Trong nhiệm vụ này, bạn sẽ tìm hiểu cách hoạt động của lớp con và tính kế thừa trong Kotlin. Các quy tắc này tương tự như những quy tắc bạn đã thấy ở các ngôn ngữ khác, nhưng vẫn có một số điểm khác biệt.

Trong Kotlin, theo mặc định, các lớp không thể được phân lớp con. Tương tự, các lớp con không thể ghi đè các thuộc tính và biến thành viên (mặc dù có thể truy cập vào các thuộc tính và biến này).

Bạn phải đánh dấu một lớp là open để cho phép lớp đó được phân lớp con. Tương tự, bạn phải đánh dấu các thuộc tính và biến thành viên là open để ghi đè chúng trong lớp con. Bạn phải dùng từ khoá open để ngăn chặn việc vô tình làm lộ chi tiết triển khai trong giao diện của lớp.

Bước 1: Mở lớp học Aquarium

Ở bước này, bạn tạo lớp Aquarium open để có thể ghi đè lớp này ở bước tiếp theo.

  1. Đánh dấu lớp Aquarium và tất cả các thuộc tính của lớp đó bằng từ khoá open.
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
    open var volume: Int
        get() = width * height * length / 1000
        set(value) {
            height = (value * 1000) / (width * length)
        }
  1. Thêm một thuộc tính shape mở có giá trị là "rectangle".
   open val shape = "rectangle"
  1. Thêm một thuộc tính water mở bằng phương thức getter trả về 90% âm lượng của Aquarium.
    open var water: Double = 0.0
        get() = volume * 0.9
  1. Thêm mã vào phương thức printSize() để in hình dạng và lượng nước dưới dạng tỷ lệ phần trăm của thể tích.
fun printSize() {
    println(shape)
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm ")
    // 1 l = 1000 cm^3
    println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
  1. Trong buildAquarium(), hãy thay đổi mã để tạo Aquarium bằng width = 25, length = 25height = 40.
fun buildAquarium() {
    val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
    aquarium6.printSize()
}
  1. Chạy chương trình của bạn và quan sát kết quả mới.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)

Bước 2: Tạo một lớp con

  1. Tạo một lớp con của Aquarium có tên là TowerTank. Lớp này triển khai một bình chứa hình trụ tròn thay vì bình chứa hình chữ nhật. Bạn có thể thêm TowerTank bên dưới Aquarium, vì bạn có thể thêm một lớp khác vào cùng một tệp với lớp Aquarium.
  2. Trong TowerTank, hãy ghi đè thuộc tính height được xác định trong hàm khởi tạo. Để ghi đè một thuộc tính, hãy dùng từ khoá override trong lớp con.
  1. Khiến hàm khởi tạo cho TowerTank lấy diameter. Sử dụng diameter cho cả lengthwidth khi gọi hàm khởi tạo trong siêu lớp Aquarium.
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
  1. Ghi đè thuộc tính thể tích để tính toán hình trụ. Công thức tính thể tích hình trụ là pi nhân với bình phương bán kính nhân với chiều cao. Bạn cần nhập hằng số PI từ java.lang.Math.
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }
  1. Trong TowerTank, hãy ghi đè thuộc tính water thành 80% âm lượng.
override var water = volume * 0.8
  1. Ghi đè shape thành "cylinder".
override val shape = "cylinder"
  1. Lớp TowerTank cuối cùng của bạn sẽ có dạng như mã bên dưới.

Aquarium.kt:

package example.myapp

import java.lang.Math.PI

... // existing Aquarium class

class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }

    override var water = volume * 0.8
    override val shape = "cylinder"
}
  1. Trong buildAquarium(), hãy tạo một TowerTank có đường kính 25 cm và chiều cao 45 cm. In kích thước.

main.kt:

package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium(width = 25, length = 25, height = 40)
    myAquarium.printSize()
    val myTower = TowerTank(diameter = 25, height = 40)
    myTower.printSize()
}
  1. Chạy chương trình rồi quan sát kết quả.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)
aquarium initializing
cylinder
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 18 l Water: 14.4 l (80.0% full)

Đôi khi, bạn muốn xác định hành vi hoặc thuộc tính chung để chia sẻ giữa một số lớp có liên quan. Kotlin cung cấp 2 cách để thực hiện việc đó, đó là giao diện và lớp trừu tượng. Trong nhiệm vụ này, bạn sẽ tạo một lớp AquariumFish trừu tượng cho các thuộc tính chung của tất cả các loài cá. Bạn tạo một giao diện có tên là FishAction để xác định hành vi chung của tất cả các loài cá.

  • Không thể tạo thực thể cho lớp trừu tượng hoặc giao diện, tức là bạn không thể tạo trực tiếp các đối tượng thuộc những loại đó.
  • Các lớp trừu tượng có hàm khởi tạo.
  • Giao diện không thể có bất kỳ logic hàm khởi tạo nào hoặc lưu trữ bất kỳ trạng thái nào.

Bước 1. Tạo một lớp trừu tượng

  1. Trong example.myapp, hãy tạo một tệp mới, AquariumFish.kt.
  2. Tạo một lớp, cũng có tên là AquariumFish, rồi đánh dấu lớp đó bằng abstract.
  3. Thêm một thuộc tính String, color và đánh dấu thuộc tính đó bằng abstract.
package example.myapp

abstract class AquariumFish {
    abstract val color: String
}
  1. Tạo 2 lớp con của AquariumFish, SharkPlecostomus.
  2. color là trừu tượng, nên các lớp con phải triển khai lớp này. Đặt Shark thành màu xám và Plecostomus thành màu vàng.
class Shark: AquariumFish() {
    override val color = "gray"
}

class Plecostomus: AquariumFish() {
    override val color = "gold"
}
  1. Trong main.kt, hãy tạo một hàm makeFish() để kiểm thử các lớp của bạn. Khởi tạo một Shark và một Plecostomus, sau đó in màu của từng đối tượng.
  2. Xoá mã kiểm thử trước đó trong main() và thêm một lệnh gọi đến makeFish(). Mã của bạn sẽ có dạng như mã dưới đây.

main.kt:

package example.myapp

fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()

    println("Shark: ${shark.color}")
    println("Plecostomus: ${pleco.color}")
}

fun main () {
    makeFish()
}
  1. Chạy chương trình rồi quan sát kết quả.
⇒ Shark: gray 
Plecostomus: gold

Sơ đồ sau đây biểu thị lớp Shark và lớp Plecostomus, trong đó phân lớp lớp trừu tượng AquariumFish.

Sơ đồ cho thấy lớp trừu tượng, AquariumFish và hai lớp con, Shark và Plecostumus.

Bước 2. Tạo một giao diện

  1. Trong AquariumFish.kt, hãy tạo một giao diện có tên là FishAction bằng phương thức eat().
interface FishAction  {
    fun eat()
}
  1. Thêm FishAction vào từng lớp con và triển khai eat() bằng cách in những gì cá làm.
class Shark: AquariumFish(), FishAction {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

class Plecostomus: AquariumFish(), FishAction {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. Trong hàm makeFish(), hãy cho mỗi con cá bạn tạo ăn một thứ gì đó bằng cách gọi eat().
fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()
    println("Shark: ${shark.color}")
    shark.eat()
    println("Plecostomus: ${pleco.color}")
    pleco.eat()
}
  1. Chạy chương trình rồi quan sát kết quả.
⇒ Shark: gray
hunt and eat fish
Plecostomus: gold
eat algae

Sơ đồ sau đây biểu thị lớp Shark và lớp Plecostomus. Cả hai lớp này đều được tạo thành từ và triển khai giao diện FishAction.

Trường hợp nên sử dụng các lớp trừu tượng so với giao diện

Các ví dụ trên rất đơn giản, nhưng khi bạn có nhiều lớp liên quan, các lớp và giao diện trừu tượng có thể giúp bạn giữ cho thiết kế của mình gọn gàng, ngăn nắp và dễ duy trì hơn.

Như đã lưu ý ở trên, các lớp trừu tượng có thể có hàm dựng, còn giao diện thì không, nhưng nếu không thì chúng rất giống nhau. Vậy khi nào bạn nên sử dụng từng loại?

Khi bạn sử dụng các giao diện để tạo một lớp, chức năng của lớp sẽ được mở rộng thông qua các thực thể lớp mà lớp đó chứa. Thành phần có xu hướng giúp mã dễ tái sử dụng và dễ hiểu hơn so với tính kế thừa từ một lớp trừu tượng. Ngoài ra, bạn có thể sử dụng nhiều giao diện trong một lớp, nhưng chỉ có thể tạo lớp con từ một lớp trừu tượng.

Thành phần thường dẫn đến đóng gói tốt hơn, liên kết (sự phụ thuộc lẫn nhau) thấp hơn, giao diện rõ ràng hơn và mã có thể sử dụng nhiều hơn. Vì những lý do này, việc sử dụng thành phần với các giao diện là lựa chọn thiết kế ưu tiên. Mặt khác, việc kế thừa từ một lớp trừu tượng có xu hướng phù hợp tự nhiên với một số vấn đề. Vì vậy, bạn nên ưu tiên thành phần, nhưng khi kế thừa có ý nghĩa, Kotlin cũng cho phép bạn làm điều đó!

  • Sử dụng một giao diện nếu bạn có nhiều phương thức và một hoặc hai cách triển khai mặc định, chẳng hạn như trong AquariumAction bên dưới.
interface AquariumAction {
    fun eat()
    fun jump()
    fun clean()
    fun catchFish()
    fun swim()  {
        println("swim")
    }
}
  • Sử dụng lớp trừu tượng bất cứ khi nào bạn không thể hoàn thành một lớp. Ví dụ: quay lại lớp AquariumFish, bạn có thể làm cho tất cả AquariumFish triển khai FishAction và cung cấp một phương thức triển khai mặc định cho eat trong khi để color ở dạng trừu tượng, vì thực sự không có màu mặc định cho cá.
interface FishAction  {
    fun eat()
}

abstract class AquariumFish: FishAction {
   abstract val color: String
   override fun eat() = println("yum")
}

Nhiệm vụ trước đó đã giới thiệu các lớp trừu tượng, giao diện và ý tưởng về thành phần. Uỷ quyền giao diện là một kỹ thuật nâng cao, trong đó các phương thức của một giao diện được triển khai bằng một đối tượng trợ giúp (hoặc đối tượng uỷ quyền), sau đó được một lớp sử dụng. Kỹ thuật này có thể hữu ích khi bạn sử dụng một giao diện trong một loạt các lớp không liên quan: bạn thêm chức năng giao diện cần thiết vào một lớp trợ giúp riêng biệt và mỗi lớp sử dụng một thực thể của lớp trợ giúp để triển khai chức năng.

Trong nhiệm vụ này, bạn sẽ sử dụng tính năng uỷ quyền giao diện để thêm chức năng vào một lớp.

Bước 1: Tạo giao diện mới

  1. Trong AquariumFish.kt, hãy xoá lớp AquariumFish. Thay vì kế thừa từ lớp AquariumFish, PlecostomusShark sẽ triển khai các giao diện cho cả hành động của cá và màu sắc của chúng.
  2. Tạo một giao diện mới, FishColor, xác định màu dưới dạng một chuỗi.
interface FishColor {
    val color: String
}
  1. Thay đổi Plecostomus để triển khai 2 giao diện, FishActionFishColor. Bạn cần ghi đè color từ FishColoreat() từ FishAction.
class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. Thay đổi lớp Shark để triển khai cả hai giao diện, FishActionFishColor, thay vì kế thừa từ AquariumFish.
class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}
  1. Mã hoàn tất của bạn sẽ có dạng như sau:
package example.myapp

interface FishAction {
    fun eat()
}

interface FishColor {
    val color: String
}

class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}

class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

Bước 2: Tạo một lớp singleton

Tiếp theo, bạn triển khai chế độ thiết lập cho phần uỷ quyền bằng cách tạo một lớp trợ giúp triển khai FishColor. Bạn tạo một lớp cơ bản có tên là GoldColor triển khai FishColor – tất cả những gì lớp này làm là cho biết màu của lớp là màu vàng.

Không có lý do gì để tạo nhiều thực thể của GoldColor, vì tất cả các thực thể đó đều sẽ làm chính xác cùng một việc. Vì vậy, Kotlin cho phép bạn khai báo một lớp mà bạn chỉ có thể tạo một thực thể bằng cách sử dụng từ khoá object thay vì class. Kotlin sẽ tạo một thực thể đó và thực thể đó được tham chiếu theo tên lớp. Sau đó, tất cả các đối tượng khác chỉ có thể sử dụng một thực thể này – không có cách nào để tạo các thực thể khác của lớp này. Nếu đã quen thuộc với mẫu singleton, thì đây là cách bạn triển khai singleton trong Kotlin.

  1. Trong AquariumFish.kt, hãy tạo một đối tượng cho GoldColor. Ghi đè màu.
object GoldColor : FishColor {
   override val color = "gold"
}

Bước 3: Thêm uỷ quyền giao diện cho FishColor

Giờ đây, bạn đã sẵn sàng sử dụng tính năng uỷ quyền giao diện.

  1. Trong AquariumFish.kt, hãy xoá chế độ ghi đè color khỏi Plecostomus.
  2. Thay đổi lớp Plecostomus để lấy màu từ GoldColor. Bạn có thể làm việc này bằng cách thêm by GoldColor vào phần khai báo lớp để tạo hoạt động uỷ quyền. Điều này có nghĩa là thay vì triển khai FishColor, hãy sử dụng phương thức triển khai do GoldColor cung cấp. Vì vậy, mỗi khi color được truy cập, yêu cầu sẽ được uỷ quyền cho GoldColor.
class Plecostomus:  FishAction, FishColor by GoldColor {
   override fun eat() {
       println("eat algae")
   }
}

Với lớp hiện tại, tất cả cá Tỳ bà đều có màu vàng, nhưng thực tế thì loài cá này có nhiều màu. Bạn có thể giải quyết vấn đề này bằng cách thêm một tham số hàm khởi tạo cho màu với GoldColor làm màu mặc định cho Plecostomus.

  1. Thay đổi lớp Plecostomus để lấy fishColor được truyền vào bằng hàm khởi tạo của lớp đó và đặt giá trị mặc định thành GoldColor. Thay đổi việc uỷ quyền từ by GoldColor thành by fishColor.
class Plecostomus(fishColor: FishColor = GoldColor):  FishAction,
       FishColor by fishColor {
   override fun eat() {
       println("eat algae")
   }
}

Bước 4: Thêm uỷ quyền giao diện cho FishAction

Tương tự, bạn có thể sử dụng tính năng uỷ quyền giao diện cho FishAction.

  1. Trong AquariumFish.kt, hãy tạo một lớp PrintingFishAction triển khai FishAction, lấy String, food, sau đó in những gì cá ăn.
class PrintingFishAction(val food: String) : FishAction {
    override fun eat() {
        println(food)
    }
}
  1. Trong lớp Plecostomus, hãy xoá hàm ghi đè eat() vì bạn sẽ thay thế hàm này bằng một uỷ quyền.
  2. Trong phần khai báo Plecostomus, hãy uỷ quyền FishAction cho PrintingFishAction, truyền "eat algae".
  3. Với tất cả các hoạt động uỷ quyền đó, không có mã nào trong phần nội dung của lớp Plecostomus, vì vậy, hãy xoá {} vì tất cả các phương thức ghi đè đều do hoạt động uỷ quyền giao diện xử lý
class Plecostomus (fishColor: FishColor = GoldColor):
        FishAction by PrintingFishAction("eat algae"),
        FishColor by fishColor

Sơ đồ sau đây biểu thị các lớp SharkPlecostomus, cả hai đều bao gồm các giao diện PrintingFishActionFishColor, nhưng uỷ quyền việc triển khai cho các giao diện này.

Uỷ quyền giao diện là một tính năng mạnh mẽ và bạn thường nên cân nhắc cách sử dụng tính năng này bất cứ khi nào có thể sử dụng một lớp trừu tượng bằng một ngôn ngữ khác. Bạn có thể dùng thành phần để cắm các hành vi, thay vì phải có nhiều lớp con, mỗi lớp chuyên biệt theo một cách khác nhau.

Lớp dữ liệu tương tự như struct trong một số ngôn ngữ khác – chủ yếu dùng để lưu giữ một số dữ liệu – nhưng đối tượng lớp dữ liệu vẫn là một đối tượng. Các đối tượng lớp dữ liệu trong Kotlin đem lại thêm một số lợi ích, chẳng hạn như các tiện ích để in và sao chép. Trong nhiệm vụ này, bạn sẽ tạo một lớp dữ liệu đơn giản và tìm hiểu về sự hỗ trợ mà Kotlin cung cấp cho các lớp dữ liệu.

Bước 1: Tạo một lớp dữ liệu

  1. Thêm một gói mới decor trong gói example.myapp để lưu trữ mã mới. Nhấp chuột phải vào example.myapp trong ngăn Project (Dự án) rồi chọn File > New > Package (Tệp > Mới > Gói).
  2. Trong gói này, hãy tạo một lớp mới có tên là Decoration.
package example.myapp.decor

class Decoration {
}
  1. Để đặt Decoration làm lớp dữ liệu, hãy thêm tiền tố cho phần khai báo lớp bằng từ khoá data.
  2. Thêm một thuộc tính String có tên là rocks để cung cấp một số dữ liệu cho lớp.
data class Decoration(val rocks: String) {
}
  1. Trong tệp, bên ngoài lớp, hãy thêm một hàm makeDecorations() để tạo và in một phiên bản của Decoration bằng "granite".
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)
}
  1. Thêm hàm main() để gọi makeDecorations() rồi chạy chương trình. Lưu ý đầu ra hợp lý được tạo vì đây là một lớp dữ liệu.
⇒ Decoration(rocks=granite)
  1. Trong makeDecorations(), hãy tạo thực thể cho 2 đối tượng Decoration khác có cùng giá trị "slate" và in các đối tượng đó.
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)

    val decoration2 = Decoration("slate")
    println(decoration2)

    val decoration3 = Decoration("slate")
    println(decoration3)
}
  1. Trong makeDecorations(), hãy thêm một câu lệnh in kết quả so sánh decoration1 với decoration2 và một câu lệnh thứ hai so sánh decoration3 với decoration2. Sử dụng phương thức equals() do các lớp dữ liệu cung cấp.
    println (decoration1.equals(decoration2))
    println (decoration3.equals(decoration2))
  1. Chạy mã.
⇒ Decoration(rocks=granite)
Decoration(rocks=slate)
Decoration(rocks=slate)
false
true

Bước 2. Sử dụng tính năng phân rã

Để truy cập vào các thuộc tính của một đối tượng dữ liệu và gán các thuộc tính đó cho các biến, bạn có thể gán từng thuộc tính một, như thế này.

val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver

Thay vào đó, bạn có thể tạo các biến (mỗi biến cho một thuộc tính) và chỉ định đối tượng dữ liệu cho nhóm biến. Kotlin đặt giá trị thuộc tính vào mỗi biến.

val (rock, wood, diver) = decoration

Đây được gọi là phân rã và là một cách viết tắt hữu ích. Số lượng biến phải khớp với số lượng thuộc tính và các biến được chỉ định theo thứ tự mà chúng được khai báo trong lớp. Sau đây là một ví dụ hoàn chỉnh mà bạn có thể thử trong Decoration.kt.

// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}

fun makeDecorations() {
    val d5 = Decoration2("crystal", "wood", "diver")
    println(d5)

// Assign all properties to variables.
    val (rock, wood, diver) = d5
    println(rock)
    println(wood)
    println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver)
crystal
wood
diver

Nếu không cần một hoặc nhiều thuộc tính, bạn có thể bỏ qua các thuộc tính đó bằng cách sử dụng _ thay vì tên biến, như minh hoạ trong mã bên dưới.

    val (rock, _, diver) = d5

Trong nhiệm vụ này, bạn sẽ tìm hiểu về một số lớp chuyên dụng trong Kotlin, bao gồm:

  • Lớp Singleton
  • Enum
  • Lớp kín

Bước 1: Gọi lại các lớp singleton

Hãy nhớ lại ví dụ trước với lớp GoldColor.

object GoldColor : FishColor {
   override val color = "gold"
}

Vì mọi thực thể của GoldColor đều làm cùng một việc, nên thực thể này được khai báo dưới dạng object thay vì class để biến thực thể này thành một singleton. Chỉ có thể có một phiên bản của nó.

Bước 2: Tạo một enum

Kotlin cũng hỗ trợ các enum, cho phép bạn liệt kê một thứ gì đó và tham chiếu đến thứ đó theo tên, giống như trong các ngôn ngữ khác. Khai báo một enum bằng cách thêm từ khoá enum vào tiền tố của khai báo. Khai báo enum cơ bản chỉ cần một danh sách tên, nhưng bạn cũng có thể xác định một hoặc nhiều trường được liên kết với mỗi tên.

  1. Trong Decoration.kt, hãy thử một ví dụ về enum.
enum class Color(val rgb: Int) {
   RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}

Enum hơi giống với singleton – chỉ có thể có một và chỉ có một trong mỗi giá trị trong quá trình liệt kê. Ví dụ: chỉ có thể có một Color.RED, một Color.GREEN và một Color.BLUE. Trong ví dụ này, các giá trị RGB được chỉ định cho thuộc tính rgb để biểu thị các thành phần màu. Bạn cũng có thể lấy giá trị thứ tự của một enum bằng thuộc tính ordinal và tên của enum bằng thuộc tính name.

  1. Hãy thử một ví dụ khác về enum.
enum class Direction(val degrees: Int) {
    NORTH(0), SOUTH(180), EAST(90), WEST(270)
}

fun main() {
    println(Direction.EAST.name)
    println(Direction.EAST.ordinal)
    println(Direction.EAST.degrees)
}
⇒ EAST
2
90

Bước 3: Tạo một lớp kín

Sealed class là một lớp có thể được tạo lớp con, nhưng chỉ ở bên trong tệp mà lớp đó được khai báo. Nếu cố gắng phân lớp lớp trong một tệp khác, bạn sẽ gặp lỗi.

Vì các lớp và lớp con nằm trong cùng một tệp, nên Kotlin sẽ biết tất cả các lớp con một cách tĩnh. Tức là tại thời gian biên dịch, trình biên dịch sẽ thấy tất cả các lớp và lớp con, đồng thời biết rằng đây là tất cả các lớp và lớp con, vì vậy trình biên dịch có thể thực hiện các bước kiểm tra bổ sung cho bạn.

  1. Trong AquariumFish.kt, hãy thử một ví dụ về lớp được niêm phong, tuân theo chủ đề dưới nước.
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()

fun matchSeal(seal: Seal): String {
   return when(seal) {
       is Walrus -> "walrus"
       is SeaLion -> "sea lion"
   }
}

Không thể phân lớp Seal trong một tệp khác. Nếu muốn thêm các loại Seal khác, bạn phải thêm chúng vào cùng một tệp. Điều này khiến các lớp được niêm phong trở thành một cách an toàn để biểu thị một số lượng kiểu cố định. Ví dụ: các lớp được niêm phong rất phù hợp để trả về trạng thái thành công hoặc lỗi từ một API mạng.

Bài học này đã đề cập đến rất nhiều nội dung. Mặc dù phần lớn kiến thức này đều quen thuộc với các ngôn ngữ lập trình hướng đối tượng khác, nhưng Kotlin có thêm một số tính năng để giữ cho mã nguồn ngắn gọn và dễ đọc.

Lớp và hàm khởi tạo

  • Xác định một lớp trong Kotlin bằng cách dùng class.
  • Kotlin tự động tạo phương thức setter và getter cho các thuộc tính.
  • Xác định hàm khởi tạo chính ngay trong định nghĩa lớp. Ví dụ:
    class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
  • Nếu hàm khởi tạo chính cần thêm mã, hãy viết mã đó trong một hoặc nhiều khối init.
  • Một lớp có thể xác định một hoặc nhiều hàm khởi tạo phụ bằng cách dùng constructor, nhưng theo kiểu Kotlin, bạn nên dùng một hàm ở trạng thái ban đầu.

Đối tượng sửa đổi chế độ hiển thị và lớp con

  • Theo mặc định, tất cả các lớp và hàm trong Kotlin đều là public, nhưng bạn có thể dùng các hệ số xác định để thay đổi mức độ hiển thị thành internal, private hoặc protected.
  • Để tạo một lớp con, bạn phải đánh dấu lớp mẹ là open.
  • Để ghi đè các phương thức và thuộc tính trong một lớp con, các phương thức và thuộc tính phải được đánh dấu là open trong lớp mẹ.
  • Bạn chỉ có thể tạo lớp con cho một lớp niêm phong trong cùng một tệp nơi lớp đó được xác định. Tạo một lớp niêm phong bằng cách thêm tiền tố sealed vào phần khai báo.

Lớp dữ liệu, singleton và enum

  • Tạo một lớp dữ liệu bằng cách thêm tiền tố data vào phần khai báo.
  • Phân rã là cách viết tắt để chỉ định các thuộc tính của một đối tượng data cho các biến riêng biệt.
  • Tạo một lớp singleton bằng cách sử dụng object thay vì class.
  • Xác định một enum bằng cách sử dụng enum class.

Lớp trừu tượng, giao diện và việc uỷ quyền

  • Lớp trừu tượng và giao diện là hai cách để chia sẻ hành vi chung giữa các lớp.
  • Lớp trừu tượng xác định các thuộc tính và hành vi, nhưng để lại việc triển khai cho các lớp con.
  • Một giao diện xác định hành vi và có thể cung cấp các phương thức triển khai mặc định cho một số hoặc tất cả hành vi.
  • Khi bạn sử dụng các giao diện để tạo một lớp, chức năng của lớp sẽ được mở rộng thông qua các thực thể lớp mà lớp đó chứa.
  • Uỷ quyền giao diện sử dụng thành phần, nhưng cũng uỷ quyền việc triển khai cho các lớp giao diện.
  • Thành phần là một cách hiệu quả để thêm chức năng vào một lớp bằng cách sử dụng uỷ quyền giao diện. Nhìn chung, bạn nên dùng thành phần, nhưng việc kế thừa từ một lớp trừu tượng sẽ phù hợp hơn với một số vấn đề.

Tài liệu về Kotlin

Nếu bạn muốn biết thêm thông tin về bất kỳ chủ đề nào trong khoá học này hoặc nếu bạn gặp khó khăn, thì https://kotlinlang.org là nơi tốt nhất để bạn bắt đầu.

Hướng dẫn về Kotlin

Trang web https://try.kotlinlang.org có các hướng dẫn phong phú được gọi là Kotlin Koans, một trình thông dịch dựa trên web và một bộ tài liệu tham khảo đầy đủ kèm theo ví dụ.

Khoá học của Udacity

Để xem khoá học của Udacity về chủ đề này, hãy xem Chương trình đào tạo về Kotlin dành cho lập trình viên.

IntelliJ IDEA

Bạn có thể xem tài liệu về IntelliJ IDEA trên trang web của JetBrains.

Phần này liệt kê các bài tập về nhà cho học viên của lớp học lập trình này trong phạm vi khoá học có người hướng dẫn. Người hướng dẫn phải thực hiện các việc sau đây:

  • Giao bài tập về nhà nếu cần.
  • Trao đổi với học viên về cách nộp bài tập về nhà.
  • Chấm điểm bài tập về nhà.

Người hướng dẫn có thể sử dụng các đề xuất này ít hoặc nhiều tuỳ ý và nên giao cho học viên bất kỳ bài tập về nhà nào khác mà họ cảm thấy phù hợp.

Nếu bạn đang tự học các lớp học lập trình, hãy sử dụng những bài tập về nhà này để kiểm tra kiến thức của mình.

Trả lời các câu hỏi sau

Câu hỏi 1

Các lớp có một phương thức đặc biệt đóng vai trò là bản thiết kế để tạo các đối tượng của lớp đó. Phương thức này có tên là gì?

▢ Một trình tạo

▢ Một trình khởi tạo

▢ Một hàm khởi tạo

▢ Bản thiết kế

Câu hỏi 2

Câu nào sau đây về giao diện và lớp trừu tượng là KHÔNG chính xác?

▢ Các lớp trừu tượng có thể có hàm khởi tạo.

▢ Giao diện không thể có hàm khởi tạo.

▢ Bạn có thể tạo trực tiếp thực thể cho giao diện và lớp trừu tượng.

▢ Các lớp con của lớp trừu tượng phải triển khai các thuộc tính trừu tượng.

Câu hỏi 3

Đối tượng sửa đổi chế độ hiển thị nào sau đây KHÔNG phải là đối tượng sửa đổi chế độ hiển thị Kotlin cho các thuộc tính, phương thức, v.v.?

internal

nosubclass

protected

private

Câu hỏi 4

Hãy xem xét lớp dữ liệu này:
data class Fish(val name: String, val species:String, val colors:String)
Đoạn mã nào sau đây KHÔNG hợp lệ để tạo và phân tách một đối tượng Fish?

val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")

val (name2, _, colors2) = Fish("Bitey", "shark", "gray")

val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")

val (name4, species4, colors4) = Fish("Harry", "halibut")

Câu hỏi 5

Giả sử bạn sở hữu một vườn thú có nhiều động vật cần được chăm sóc. Mục nào sau đây KHÔNG thuộc quy trình triển khai hoạt động chăm sóc?

▢ Một interface cho các loại thức ăn mà động vật ăn.

▢ Một lớp abstract Caretaker mà bạn có thể tạo nhiều loại người chăm sóc.

▢ Một interface để cung cấp nước sạch cho động vật.

▢ Lớp data cho một mục trong lịch cho ăn.

Chuyển sang bài học tiếp theo: 5.1 Phần mở rộng

Để biết thông tin tổng quan về khoá học này, bao gồm cả đường liên kết đến các lớp học lập trình khác, hãy xem "Chương trình đào tạo về Kotlin dành cho lập trình viên: Chào mừng bạn đến với khoá học".