Chương trình rèn luyện 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 khóa học về Kotlin dành cho lập trình viên. Bạn sẽ nhận được nhiều giá trị nhất từ khóa học này nếu bạn làm việc qua các lớp học lập trình theo trình tự. Tùy thuộc vào kiến thức của mình, bạn có thể đọc lướt một số phần. Khóa học này dành cho những lập trình viên biế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 tạo một chương trình trong Kotlin và tìm hiểu về các lớp và đối tượng trong Kotlin. Phần lớn nội dung này sẽ quen thuộc với bạn nếu bạn biết một ngôn ngữ khác hướng đối tượng, nhưng 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ề lớp trừu tượng và hoạt động ủy quyền giao diện.

Thay vì xây dựng một ứng dụng mẫu duy nhất, các bài học trong khóa học này được thiết kế để trang bị cho bạn kiến thức nhưng hãy độc lập với nhau để có thể đọc lướt các phần mà bạn quen thuộc. Nhiều ví dụ sử dụng chủ đề thủy cung để liên kết chúng với nhau. Và nếu bạn muốn xem toàn bộ câu chuyện về thủy cung, hãy xem khóa học Kotlin Bootcamp dành cho lập trình viên Udacity.

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

  • Khái niệm cơ bản về Kotlin, bao gồm cả các loại, toán tử và vòng lặp
  • Cú pháp hàm của Kotlin\39;s
  • Kiến thức cơ bản về lập trình hướng đối tượng
  • Các khái niệm cơ bản về 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 các hàm dựng lớp trong Kotlin
  • Cách tạo lớp con và cách tính năng kế thừa hoạt động
  • Giới thiệu về lớp trừu tượng, giao diện và ủy quyền giao diện
  • Cách tạo và sử dụng lớp dữ liệu
  • Cách sử dụng singleton, enum và các lớp được đóng kín

Bạn sẽ thực hiện

  • Tạo một lớp có thuộc tính
  • Tạo một hàm dựng cho một lớp
  • Tạo lớp con
  • Xem ví dụ về các 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à các lớp được đóng dấu

Những thuật ngữ lập trình sau đây chắc hẳn đã quen thuộc với bạn:

  • Lớp là sơ đồ thiết kế của các đối tượng. Ví dụ: lớp Aquarium là sơ đồ thiết kế để tạo một đối tượng bể cá.
  • Đối tượng là các thực thể của lớp; đối tượng trong bể cá là một thực tế trong Aquarium.
  • Tài sản là các đặc điểm của lớp, chẳng hạn như độ dài, chiều rộng và chiều cao của Aquarium.
  • Phương thức, còn được gọi là hàm thành viên, là chức năng của lớp. Phương pháp là việc bạn có thể "do" với đối tượng. Ví dụ: bạn có thể fillWithWater() một đối tượng Aquarium.
  • Giao diện là thông số kỹ thuật mà một lớp có thể triển khai. Ví dụ: việc vệ sinh thường là đối với các đồ vật khác ngoài thủy cung và việc vệ sinh thường diễn ra tương tự như đối với những đồ vật khác. Vì vậy, bạn có thể có một giao diện có tên là Clean để xác định 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 một miếng bọt biển mềm.
  • Gói là một cách để nhóm các 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 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ư lớp học trong gói.

Trong nhiệm vụ này, bạn 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 gói

Các gói có thể giúp bạn sắp xếp mã gọn gàng.

  1. Trong ngăn 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 > Package và gọi gói này là example.myapp.

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

Lớp được xác định bằng từ khóa class và tên lớp theo quy ước bắt đầu bằng chữ hoa.

  1. Nhấp chuột phải vào gói example.myapp.
  2. Chọn New > Kotlin File / class.
  3. Trong phần Loại, hãy chọn Lớp học rồi đặt tên cho lớp đó Aquarium. IntelliJ IDEA đưa 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 có giá trị mặc định.
package example.myapp

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

Về cơ bản, Kotlin sẽ tự động tạo các phương thức getter và setter cho các 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, ví dụ: 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 để giữ hàm main().

  1. Trong ngăn 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.
  3. Trong trình đơn thả xuống Loại, hãy giữ lựa chọn là Tệp và đặt tên tệp là main.kt. IntelliJ IDEA có tên gói nhưng không bao gồm định nghĩa lớp cho một tệp.
  4. Xác định hàm buildAquarium() và bên trong tạo một thực thể của Aquarium. Để tạo một thực thể, 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 dựng 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 phương thức

  1. Trong lớp Aquarium, hãy thêm một phương thức để in thuộc tính kích thước của thủy cung.
    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 và 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 nhiệm vụ này, bạn tạo một hàm dựng 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 hàm dựng

Trong bước này, bạn thêm một hàm dựng vào lớp Aquarium mà bạn đã tạo trong việc cần làm đầu tiên. Trong ví dụ trước đó, mọi bản sao của Aquarium đều được tạo với cùng phương diện. Bạn có thể thay đổi thứ nguyên sau khi tạo nó bằng cách đặt các thuộc tính, nhưng sẽ dễ dàng hơn để tạo kích thước chính xác để bắt đầu.

Trong một số ngôn ngữ lập trình, hàm dựng đượ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 dựng ngay trong 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, các thông số đó có thể bao gồm 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 thông số hàm dựng có giá trị mặc định cho length, widthheight, rồi gán 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 viết gọn hơn của Kotlin là xác định các thuộc tính trực tiếp với hàm dựng, sử dụng var hoặc val, còn Kotlin cũng tự động tạo các phương thức getter và setter. Sau đó, bạn có thể xóa 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 dựng đó, bạn có thể chỉ định không có đối số nào và nhận giá trị mặc định, hoặc chỉ chỉ định một số đối số trong số đó hoặc chỉ định tất cả đối số và tạo một Aquarium có kích thước hoàn toàn tùy 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ê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 

Xin lưu ý rằng bạn không phải làm quá tải hàm dựng và viết một phiên bản khác cho mỗi trường hợp này (cộng thêm một vài hàm 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à các thông số đã đặt tên.

Bước 2: Thêm khối init

Các hàm dựng mẫu ở trên chỉ khai báo thuộc tính và chỉ định giá trị của một biểu thức cho chúng. Nếu hàm dựng của bạn cần thêm mã khởi tạo, thì bạn có thể đặt hàm dựng đó trong một hoặc nhiều khối init. Trong bước này, bạn 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à khối thứ hai để in thể tích theo 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 được thực thi khi hàm dựng được gọi.

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

Ở bước này, bạn sẽ tìm hiểu về các hàm dựng phụ và thêm một hàm dựng vào lớp của mình. Ngoài một hàm dựng 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 dựng phụ để cho phép hàm dựng quá tải, tức là các hàm dựng có đối số khác nhau.

  1. Trong lớp Aquarium, hãy thêm một hàm dựng phụ lấy một số loại cá làm đối số, sử dụng từ khóa constructor. Tạo một thuộc tính bể val cho thể tích bể cá tính theo lít dựa trên số lượng cá. Giả định 2 lít nước (2.000 cm^3) nước trên mỗi con cá, cộng thêm một khoảng trống nhỏ để 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. Bên trong hàm dựng phụ, hãy giữ nguyên chiều dài và chiều rộng (đã đặt trong hàm dựng chính) và tính toán chiều cao cần thiết để đặt thùng chứa thể tích đã cho.
    // calculate the height needed
    height = (tank / (length * width)).toInt()
  1. Trong hàm buildAquarium(), hãy thêm lệnh gọi để tạo Aquarium bằng cách sử dụng hàm dựng 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 và 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 khối lượng này sẽ đượ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ể đưa từ khóa constructor vào hàm dựng chính, nhưng bạn không cần dùng từ khóa này trong hầu hết các trường hợp.

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

Trong bước này, bạn sẽ thêm một phương thức getter của thuộc tính rõ ràng. Kotlin tự động xác định phương thức getter và setter khi bạn xác định các thuộc tính, nhưng đôi khi cần điều chỉnh hoặc tính toán giá trị của một thuộc tính. Ví dụ: bạn đã in tập của Aquarium. Bạn có thể cung cấp khối lượng dưới dạng tài sản bằng cách xác định một biến và phương thức getter cho biến đó. Vì volume cần được tính toán, phương thức getter cần trả về giá trị đã tính, mà bạn có thể thực hiện bằng 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 phương thức get() để tính khối lượng trong dòng tiếp theo.
val volume: Int
    get() = width * height * length / 1000  // 1000 cm^3 = 1 l
  1. Xóa khối init in ra âm lượng.
  2. Xóa mã trong buildAquarium() sẽ 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 và 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 giống như trước, nhưng thể tích chỉ được in một lần sau khi đối tượng được khởi tạo hoàn toàn bằng cả hàm dựng chính và hàm dựng phụ.

Bước 5: Thêm phương thức setter thuộc tính

Trong bước này, bạn sẽ tạo một phương thức setter mới cho khối lượng.

  1. Trong lớp Aquarium, hãy thay đổi giá trị của volume thành var để có thể đặt giá trị này nhiều lần.
  2. Thêm một phương thức setter cho thuộc tính volume bằng cách thêm phương thức set() bên dưới phương thức 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 đã cung cấp. Theo quy ước, tên của thông số setter là value, nhưng bạn có thể thay đổi thông số 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 Thủy cung là 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 của bạn và quan sát chiều cao và âm lượng đã 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

Không có công cụ sửa đổi hiển thị nào, chẳng hạn như public hoặc private, trong mã cho đến nay. Lý do là: theo mặc định, mọi thứ trong Kotlin đều ở chế độ công khai, nghĩa là mọi thứ đều có thể truy cập được ở mọi nơi, bao gồm 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ó những từ khóa công cụ sửa đổi chế độ hiển thị:

  • public có 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 có nghĩa là bộ lọc sẽ chỉ hiển thị trong học phần đó. Mô-đun là một tập hợp các tệp Kotlin được biên dịch cùng nhau, ví dụ: một thư viện hoặc ứng dụng.
  • private có nghĩa là tệp sẽ 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 với private, nhưng cũng sẽ hiển thị với mọi lớp con.

Xem Công cụ sửa đổi chế độ hiển thị trong tài liệu về Kotlin để biết thêm thông tin.

Biến thành viên

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

Nếu bạn muốn chỉ định hoặc đọc mã thuộc tính, nhưng mã bên ngoài chỉ có thể đọc, thì bạn có thể để thuộc tính và phương thức getter là công khai và khai báo phương thức setter ở chế độ riêng tư, như được hiển thị bên dưới.

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. Những ngôn ngữ này tương tự như những gì bạn đã thấy trong các ngôn ngữ khác, nhưng có một số điểm khác biệt.

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

Bạn phải đánh dấu một lớp là open để cho phép 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 phần là open để ghi đè các biến đó trong lớp con. Cần có từ khóa open để ngăn việc vô tình làm rò rỉ thông tin triển khai trong giao diện của lớp học.

Bước 1: Đặt lớp học cho Thủy cung mở

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

  1. Đánh dấu lớp Aquarium và tất cả thuộc tính của lớp đó bằng từ khóa 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 đang mở có giá trị "rectangle".
   open val shape = "rectangle"
  1. Thêm một thuộc tính water đang mở có 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 phần trăm 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 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 lớp con

  1. Tạo một lớp con của Aquarium có tên là TowerTank. Lớp này sẽ triển khai một bể hình trụ tròn thay vì bể 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 trong cùng 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 dựng. Để ghi đè một tài sản, hãy dùng từ khóa override trong lớp con.
  1. Tạo hàm dựng cho TowerTank lấy diameter. Dùng diameter cho cả lengthwidth khi gọi hàm dựng trong lớp cao cấ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 một hình trụ. Công thức của một hình trụ bằng pi nhân với bình phương của 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 phải 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 là 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 và 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 phổ biến để 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 tạo một lớp AquariumFish trừu tượng cho những thuộc tính phổ biến đối với tất cả cá. Bạn tạo một giao diện có tên là FishAction để xác định hành vi chung cho tất cả các loại cá.

  • Cả lớp trừu tượng lẫn giao diện đều không thể tạo thực thể của riêng nó, điều đó có nghĩa là bạn không thể tạo trực tiếp đối tượng của những loại đó.
  • Lớp trừu tượng có hàm dựng.
  • Các giao diện không được có logic logic hoặc lưu bất kỳ trạng thái nào.

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

  1. Trong phần example.myapp, hãy tạo một tệp mới, AquariumFish.kt.
  2. Tạo một lớp, còn gọi là AquariumFish và đánh dấu lớp đó bằng abstract.
  3. Hãy 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 hai lớp con là AquariumFish, SharkPlecostomus.
  2. color là trừu tượng, các lớp con phải triển khai hàm này. Đặt Shark màu xám và Plecostomus vàng.
class Shark: AquariumFish() {
    override val color = "gray"
}

class Plecostomus: AquariumFish() {
    override val color = "gold"
}
  1. Trong tệp main.kt, hãy tạo một hàm makeFish() để kiểm tra các lớp học của bạn. Hãy tạo bản sao cho SharkPlecostomus, sau đó in màu của từng bản xem trước.
  2. Xóa mã thử nghiệm trước đó của bạn trong main() và thêm cuộc gọi vào makeFish(). Mã của bạn phải trông giống như mã bên dưới.

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 và quan sát kết quả.
⇒ Shark: gray 
Plecostomus: gold

Sơ đồ dưới đây biểu thị lớp Shark và lớp Plecostomus, các lớp này phân lớp con cho lớp trừu tượng, AquariumFish.

Sơ đồ hiển thị lớp trừu tượng bể cá, hai lớp con là Shark và Plecostumus.

Bước 2. Tạo 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 cho phép máy in thực hiện chức năng của cá.
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 yêu cầu từng con cá bạn tạo ra ăn 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 và quan sát kết quả.
⇒ Shark: gray
hunt and eat fish
Plecostomus: gold
eat algae

Sơ đồ dưới đây đại diện cho lớp Shark và lớp Plecostomus, cả hai đều bao gồm và triển khai giao diện FishAction.

Trường hợp sử dụng 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, lớp và giao diện trừu tượng có thể giúp bạn thiết kế gọn gàng, ngăn nắp và dễ bảo trì hơn.

Như đã lưu ý ở trên, các lớp trừu tượng có thể có các hàm dựng và giao diện không thể, nhưng nếu không thì các hàm này tương tự nhau. Vì vậy, khi nào bạn nên sử dụng tính năng này?

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

Thành phần thường dẫn đến khả năng đóng gói tốt hơn, kết nối thấp hơn (phụ thuộc), giao diện sạch hơn và mã dễ sử dụng hơn. Vì những lý do này, nên sử dụng bản sáng tác cùng với giao diệ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 bản sáng tác, nhưng khi việc kế thừa có ý nghĩa thì Kotlin cũng cho phép bạn làm điều đó!

  • Hãy 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, ví dụ như trong AquariumAction dưới đây.
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 tất lớp học. Ví dụ: quay trở lại lớp AquariumFish, bạn có thể thực hiện tất cả AquariumFish triển khai FishAction và cung cấp cách triển khai mặc định cho eat trong khi vẫn để color trừu tượng, bởi vì thực sự không phải là 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 đó là giới thiệu các lớp học, giao diện và ý tưởng sáng tác. Ủy quyền giao diện là một kỹ thuật nâng cao, trong đó, phương thức của một giao diện được triển khai bởi đối tượng trợ giúp (hoặc ủy quyền), mà sau đó một lớp sẽ dùng. Kỹ thuật này có thể hữu ích khi bạn sử dụng giao diện trong một chuỗi 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ình trợ giúp riêng và mỗi lớp sử dụng một bản sao của lớp trình trợ giúp để triển khai chức năng này.

Trong nhiệm vụ này, bạn dùng tính năng ủy 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 xóa lớp AquariumFish. Thay vì kế thừa từ lớp AquariumFish, PlecostomusShark sẽ triển khai giao diện cho cả hành động cá và màu sắc của chúng.
  2. Tạo giao diện mới, FishColor, xác định màu dưới dạng chuỗi.
interface FishColor {
    val color: String
}
  1. Thay đổi Plecostomus để triển khai hai 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. Hãy thay đổi lớp Shark để triển khai cả hai giao diện là 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ẽ trông giố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 lớp singleton

Tiếp theo, bạn sẽ triển khai quá trình thiết lập cho phần ủy quyền bằng cách tạo một lớp trợ giúp sẽ triển khai FishColor. Bạn tạo một lớp cơ bản có tên là GoldColor. Lớp này triển khai FishColor – tức là lớp chỉ có màu vàng.

Việc tạo nhiều phiên bản GoldColor sẽ không hợp lý, vì tất cả đều hoạt động giống nhau. 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 phiên bản của lớp bằng cách sử dụng từ khóa object thay vì class. Kotlin sẽ tạo 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 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 bạn quen 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 ủy quyền giao diện cho FishColor

Bây giờ bạn đã sẵn sàng sử dụng ủy quyền giao diện.

  1. Trong tệp AquariumFish.kt, hãy xóa tùy chọn ghi đè color khỏi Plecostomus.
  2. Thay đổi lớp Plecostomus để nhận màu từ GoldColor. Bạn thực hiện việc này bằng cách thêm by GoldColor vào phần khai báo lớp, tạo ủy 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 lần truy cập vào color, hệ thống sẽ ủy quyền cho GoldColor.
class Plecostomus:  FishAction, FishColor by GoldColor {
   override fun eat() {
       println("eat algae")
   }
}

Với lớp học như vậy, tất cả loài Plecos sẽ có màu vàng, nhưng những loài cá này thực sự có nhiều màu sắc. Bạn có thể giải quyết vấn đề này bằng cách thêm tham số hàm dựng cho màu, trong đó GoldColor làm màu mặc định cho Plecostomus.

  1. Thay đổi lớp Plecostomus để nhận đường dẫn trong fishColor bằng hàm dựng, rồi đặt giá trị mặc định là GoldColor. Thay đổi chế độ ủy 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 ủy quyền giao diện cho FishAction

Tương tự như vậy, bạn có thể dùng tính năng ủy 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ớp này sẽ nhận 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 xóa hàm ghi đè eat() vì bạn sẽ thay thế hàm này bằng một thực thể đại diện.
  2. Trong khai báo Plecostomus, hãy uỷ quyền FishAction cho PrintingFishAction, vượt qua "eat algae".
  3. Với tất cả sự ủy quyền đó, sẽ không có mã nào trong phần nội dung của lớp Plecostomus, do đó, hãy xóa {} vì mọi thao tác ghi đè đều được xử lý bằng việc ủy quyền giao diện
class Plecostomus (fishColor: FishColor = GoldColor):
        FishAction by PrintingFishAction("eat algae"),
        FishColor by fishColor

Sơ đồ dưới đây đại diện cho các lớp SharkPlecostomus, cả hai đều bao gồm giao diện PrintingFishActionFishColor, nhưng ủy quyền triển khai cho các lớp đó.

Việc ủy quyền giao diện rất hữu ích và bạn thường nên xem xét cách sử dụng tính năng này bất cứ khi nào có thể sử dụng lớp trừu tượng bằng một ngôn ngữ khác. Tính năng này cho phép bạn dùng bố cục để thêm các hành vi, thay vì yêu cầu nhiều lớp con, mỗi lớp con chuyên dùng 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 – lớp này chủ yếu tồn tại để lưu 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 Kotlin có thêm một số lợi ích, chẳng hạn như tiện ích in và sao chép. Trong nhiệm vụ này, bạn 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 decor mới trong gói example.myapp để giữ mã mới. Nhấp chuột phải vào example.myapp trong ngăn Project rồi chọn File > New > Package.
  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 từ khóa data vào phần khai báo lớp.
  2. Thêm một thuộc tính String có tên là rocks để cung cấp cho lớp một số dữ liệu.
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 thực thể của Decoration bằng "granite".
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)
}
  1. Thêm một hàm main() để gọi makeDecorations() và chạy chương trình của bạn. Lưu ý rằng kết quả hợp lệ được tạo vì đây là lớp dữ liệu.
⇒ Decoration(rocks=granite)
  1. Trong makeDecorations(), hãy tạo thêm 2 đối tượng Decoration vừa là "slate" vừa in.
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 để in kết quả so sánh decoration1 với decoration2 và câu lệnh thứ hai so sánh decoration3 với decoration2. 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 cấu trúc phá hủy

Để nhận thuộc tính của một đối tượng dữ liệu và chỉ định các biến đó cho các biến, bạn có thể chỉ định từng biến thể 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ị của thuộc tính vào mỗi biến.

val (rock, wood, diver) = decoration

Hành động này được gọi là hủy cấu trúc và là một cách rút gọn hữu ích. Số biến phải khớp với số thuộc tính và các biến được chỉ định theo thứ tự khai báo trong lớp. Dưới đây là một ví dụ hoàn chỉnh mà bạn có thể thử trong tệp 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ư hiển thị 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 học singleton
  • Enum
  • Lớp học kín

Bước 1: Nhắc lại các lớp singleton

Dùng lại lớp GoldColor trong ví dụ trước.

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

Vì mọi thực thể của GoldColor đều làm điều tương tự, nên thực thể đó được khai báo là object thay vì class để biến giá trị đó thành singleton. Chỉ có thể có một trường hợp.

Bước 2: Tạo một danh sách

Kotlin cũng hỗ trợ giá trị enum, cho phép bạn liệt kê thứ gì đó và tham chiếu đến tên đó theo tên, giống như trong các ngôn ngữ khác. Khai báo một giá trị enum bằng cách đặt tiền tố enum cho từ khai báo. Việc khai báo liệt kê cơ bản chỉ cần một danh sách tên. Tuy nhiên, bạn cũng có thể xác định một hoặc nhiều trường 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);
}

Bảng liệt kê cũng giống một singleton, chỉ có thể có một và chỉ có một giá trị trong bảng 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 gán cho thuộc tính rgb để đại diện cho các thành phần màu. Bạn cũng có thể nhận giá trị thứ tự của một giá trị enum bằng thuộc tính ordinal và tên của thuộc tính này bằng cách sử dụ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

Lớp được đóng dấu là một lớp có thể phân 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 con cho 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 thuộc cùng một tệp, nên Kotlin sẽ biết tĩnh tất cả các lớp con. Đó là vào thời gian biên dịch, trình biên dịch xem tất cả các lớp và lớp con và biết rằng đây là tất cả lớp, vì vậy trình biên dịch có thể kiểm tra thêm cho bạn.

  1. Trong AquariumFish.kt, hãy thử một ví dụ về lớp học được đóng kín, theo chủ đề 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"
   }
}

Lớp Seal không thể phân lớp con 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 các loại đó vào cùng một tệp. Điều này giúp các lớp bịt kín trở thành một cách an toàn để đại diện cho số lượng loại cố định. Ví dụ: các lớp kín rất phù hợp để trả về thành công hoặc lỗi từ API mạng.

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

Lớp và hàm dựng

  • 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 dựng chính ngay trong phần xác định lớp. Ví dụ:
    class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
  • Nếu một hàm dựng chính cần thêm mã, hãy viết mã đó trong một hoặc nhiều khối init.
  • Lớp có thể xác định một hoặc nhiều hàm dựng phụ bằng cách sử dụng constructor, nhưng kiểu Kotlin là dùng một hàm gốc.

Từ khóa xác định mức độ hiển thị và các lớp con

  • Tất cả các lớp và hàm trong Kotlin đều là public theo mặc định. Tuy nhiên, bạn có thể dùng từ khóa xác định để thay đổi chế độ hiển thị thành internal, private hoặc protected.
  • Để tạo lớp con, lớp mẹ phải được đánh dấu là open.
  • Để ghi đè các phương thức và thuộc tính trong 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ẹ.
  • Một lớp bị kín chỉ có thể được phân lớp con trong cùng một tệp mà lớp đó được xác định. Tạo một lớp kín bằng cách đặt tiền tố khai báo bằng sealed.

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

  • Tạo một lớp dữ liệu bằng cách đặt tiền tố khai báo bằng data.
  • Định cấu trúc là cách viết ngắn để chỉ định các thuộc tính của đố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 giá trị enum bằng enum class.

Lớp, giao diện và ủy quyền trừu tượng

  • 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 để triển khai các lớp con.
  • Giao diện xác định hành vi và có thể cung cấp 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 giao diện để soạn lớp, chức năng của lớp được mở rộng thông qua các thực thể lớp có trong đó.
  • Ủy quyền giao diện sử dụng thành phần, nhưng cũng ủy quyền 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 ủy quyền giao diện. Ưu tiên chung về thành phần là ưu tiên nhưng việc kế thừa từ lớp trừu tượng là phù hợp hơn đối 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 khóa học này hoặc nếu bạn gặp khó khăn, https://kotlinlang.org là điểm khởi đầu tốt nhất của bạn.

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 biên dịch viên dựa trên web và một bộ tài liệu tham khảo đầy đủ có các ví dụ.

Khóa học Udacity

Để xem khóa học Udacity về chủ đề này, hãy xem Kotlin Bootcamp dành cho lập trình viên.

IntelliJ IDEA

Bạn có thể tìm thấy 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à có thể được giao cho học viên đang làm việc qua lớp học lập trình này trong khóa học do người hướng dẫn tổ chức. Người hướng dẫn có thể làm những việc sau:

  • Giao bài tập về nhà nếu được yêu cầu.
  • Trao đổi với học viên 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 những đề xuất này ít hay nhiều tùy ý. Do đó, họ có thể thoải mái giao 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ự mình làm việc qua lớp học lập trình này, hãy thoải mái sử dụng các bài tập về nhà này để kiểm tra kiến thức của bạn.

Trả lời những câu hỏi này

Câu hỏi 1

Lớp có một phương thức đặc biệt đóng vai trò là một sơ đồ thiết kế để tạo các đối tượng của lớp đó. Phương pháp gọi là gì?

▢ Trình tạo

▢ Trình tạo trình tạo

▢ Hàm dựng

▢ Sơ đồ 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 KHÔNG đúng?

▢ Lớp trừu tượng có thể có hàm dựng.

▢Giao diện không thể có hàm dựng.

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

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

Câu hỏi 3

Câu nào sau đây KHÔNG phải là công cụ sửa đổi chế độ hiển thị Kotlin cho các tài sản, 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)
Đâu là mã KHÔNG hợp lệ để tạo và định cấu trúc đố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 mà tất cả chúng ta cần phải chăm sóc. Câu nào sau đây KHÔNG thuộc quá trình triển khai hoạt động chăm sóc?

interface để xem nhiều loại thực phẩm mà động vật ăn.

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

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

▢ Một lớp data cho một mục trong lịch biểu cho nguồn cấp dữ liệu.

Chuyển sang bài học tiếp theo: 5.1 Tiện ích

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