Sử dụng các lớp và đối tượng trong Kotlin

1. Trước khi bắt đầu

Lớp học lập trình này hướng dẫn bạn cách sử dụng các lớp và đối tượng trong Kotlin.

Các lớp cung cấp bản thiết kế để tạo dựng đối tượng. Đối tượng là một thực thể của lớp bao gồm dữ liệu dành riêng cho đối tượng đó. Bạn có thể dùng các đối tượng hoặc thực thể lớp thay thế cho nhau.

Tương tự như vậy, hãy tưởng tượng bạn đang xây một ngôi nhà. Giống như bản vẽ của kiến trúc sư, lớp còn được gọi là bản thiết kế. Bản thiết kế tự thân nó không phải là một ngôi nhà mà là hướng dẫn về cách xây nhà. Ngôi nhà là vật thể thực hoặc là một đối tượng được xây dựng dựa trên bản thiết kế.

Giống như bản thiết kế nhà, có nhiều phòng được thiết kế với mục đích sử dụng riêng, mỗi phòng có thiết kế và mục đích riêng. Để biết cách thiết kế các lớp, bạn cần làm quen với cách lập trình hướng đối tượng (OOP), một khung hướng dẫn bạn đóng gói dữ liệu, logic và hành vi vào trong đối tượng.

OOP giúp bạn đơn giản hoá các vấn đề phức tạp trong thế giới thực thành những đối tượng nhỏ hơn. Có bốn khái niệm cơ bản về OOP mà bạn sẽ tìm hiểu thêm trong lớp học lập trình này:

  • Đóng gói. Bao gồm các thuộc tính và phương thức liên quan giúp thực hiện hành động trên các thuộc tính đó trong một lớp. Lấy điện thoại di động của bạn làm ví dụ. Sản phẩm này bao gồm một máy ảnh, màn hình, thẻ nhớ cùng một số phần cứng và phần mềm khác. Bạn không cần phải lo lắng về cách các thành phần bên trong kết hợp với nhau.
  • Tính trừu tượng. Là phần mở rộng của việc đóng gói. Với mục đích là ẩn logic triển khai bên trong càng nhiều càng tốt. Ví dụ: để chụp ảnh bằng điện thoại di động, bạn chỉ cần mở ứng dụng máy ảnh, hướng điện thoại đến nơi muốn chụp và nhấp vào nút để chụp ảnh. Bạn không cần phải biết cách phát triển ứng dụng máy ảnh hoặc phần cứng máy ảnh trên điện thoại di động thực tế hoạt động như thế nào. Tóm lại, cơ chế nội bộ của ứng dụng máy ảnh và cách máy ảnh trên thiết bị di động chụp ảnh sẽ được tóm tắt để bạn thực hiện các tác vụ cần thiết.
  • Tính kế thừa (inheritance). Cho phép bạn tạo lớp dựa trên đặc điểm và hành vi của các lớp khác bằng cách thiết lập mối quan hệ cha-con. Ví dụ: có nhiều nhà sản xuất chế tạo ra nhiều thiết bị di động chạy Android OS nhưng giao diện người dùng cho từng thiết bị là khác nhau. Nói cách khác, nhà sản xuất kế thừa tính năng của hệ điều hành Android và tuỳ chỉnh dựa trên tính năng đó.
  • Tính đa hình. Từ này được phỏng theo và có nguồn gốc từ Hy Lạp poly-, có nghĩa là nhiều, và -morphism, là các dạng. Tính đa hình là khả năng sử dụng các đối tượng khác nhau theo một cách chung. Ví dụ: khi bạn kết nối loa Bluetooth với điện thoại di động, điện thoại chỉ cần biết có một thiết bị có thể phát âm thanh bằng Bluetooth. Tuy nhiên, có nhiều loa Bluetooth để bạn chọn và điện thoại không cần phải biết cụ thể cách làm việc với từng loa.

Cuối cùng, bạn sẽ tìm hiểu về các lớp uỷ quyền thuộc tính. Đây là những lớp cung cấp mã có thể sử dụng lại để quản lý các giá trị thuộc tính bằng một cú pháp ngắn gọn. Trong lớp học lập trình này, bạn sẽ học những khái niệm này khi xây dựng cấu trúc lớp cho ứng dụng nhà thông minh.

Điều kiện tiên quyết

  • Cách mở, chỉnh sửa và chạy mã trong Kotlin Playground.
  • Kiến thức cơ bản về lập trình Kotlin, bao gồm các biến, hàm và hàm println()main()

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

  • Tổng quan về OOP.
  • Định nghĩa về lớp.
  • Cách xác định lớp bằng hàm khởi tạo, hàm và thuộc tính.
  • Cách tạo thực thể cho một đối tượng.
  • Định nghĩa về tính kế thừa.
  • Sự khác biệt giữa mối quan hệ IS-A và HAS-A.
  • Cách ghi đè thuộc tính và hàm.
  • Công cụ sửa đổi chế độ hiển thị là gì.
  • Ủy quyền là gì và cách sử dụng ủy quyền by.

Sản phẩm bạn sẽ tạo ra

  • Cấu trúc lớp trong nhà thông minh.
  • Lớp đại diện cho các thiết bị thông minh, chẳng hạn như TV thông minh và đèn thông minh.

Bạn cần có

  • Một chiếc máy tính có kết nối Internet và trình duyệt web

2. Xác định một lớp

Khi xác định một lớp, bạn sẽ chỉ định các thuộc tính và phương thức tất cả đối tượng của lớp đó phải có.

Định nghĩa lớp bắt đầu bằng từ khoá class, theo sau là tên và cặp dấu ngoặc nhọn. Phần cú pháp trước dấu ngoặc nhọn cũng được gọi là tiêu đề lớp. Trong dấu ngoặc nhọn, bạn có thể chỉ định các thuộc tính và hàm cho lớp. Bạn sẽ sớm tìm hiểu về các thuộc tính và hàm. Bạn có thể xem cú pháp của định nghĩa lớp trong sơ đồ này:

Định nghĩa này bắt đầu bằng từ khoá của lớp, theo sau là tên, cặp dấu ngoặc nhọn. Dấu ngoặc nhọn chứa phần nội dung của lớp mô tả bản thiết kế của lớp đó.

Dưới đây là những quy ước đặt tên được đề xuất cho một lớp:

  • Bạn có thể chọn bất kỳ tên lớp nào mình muốn, nhưng không được dùng từ khóa Kotlin làm tên lớp, chẳng hạn như từ khóa fun.
  • Tên lớp được viết bằng PascalCase, vì vậy, mỗi từ bắt đầu bằng một chữ cái viết hoa và không có dấu cách giữa các từ. Ví dụ: trong SmartDevice, chữ cái đầu tiên của mỗi từ được viết hoa và không có dấu cách giữa các từ.

Một lớp bao gồm 3 phần chính:

  • Properties (Thuộc tính). Các biến chỉ định thuộc tính của đối tượng của lớp.
  • Phương thức. Các hàm chứa hành vi và hành động của lớp.
  • Hàm khởi tạo Một hàm thành viên đặc biệt tạo các thực thể của lớp trong toàn bộ chương trình định nghĩa hàm này.

Đây không phải là lần đầu tiên bạn làm việc với các lớp. Trong các lớp học lập trình trước đây, bạn đã tìm hiểu về các loại dữ liệu, chẳng hạn như loại dữ liệu Int, Float, StringDouble. Các loại dữ liệu này được xác định là lớp trong Kotlin. Khi xác định một biến như trong đoạn mã dưới đây, bạn sẽ tạo một đối tượng của lớp Int. Lớp này được tạo thực thể bằng giá trị 1:

val number: Int = 1

Xác định một lớp SmartDevice:

  1. Trong Kotlin Playground, hãy thay nội dung bằng một hàm main() trống:
fun main() {
}
  1. Trên dòng trước hàm main(), hãy xác định lớp SmartDevice có nội dung bao gồm một nhận xét // empty body:
class SmartDevice {
    // empty body
}

fun main() {
}

3. Tạo một thực thể của lớp

Như bạn đã biết, lớp là bản thiết kế của một đối tượng. Thời gian chạy Kotlin sử dụng lớp hoặc bản thiết kế để tạo một đối tượng thuộc loại cụ thể đó. Với lớp SmartDevice, bạn sẽ có bản thiết kế của một thiết bị thông minh. Để có thiết bị thông minh thực tế trong chương trình, bạn cần tạo một thực thể đối tượng SmartDevice. Cú pháp tạo thực thể bắt đầu bằng tên lớp, theo sau là một cặp dấu ngoặc đơn như có thể thấy trong sơ đồ dưới đây:

1d25bc4f71c31fc9.png

Để sử dụng một đối tượng, bạn sẽ tạo đối tượng và gán đối tượng đó cho một biến, tương tự như cách bạn xác định một biến. Bạn dùng từ khoá val để tạo biến không thể thay đổi và từ khoá var cho biến có thể thay đổi. Từ khoá val hoặc var theo sau là tên của biến, sau đó là một toán tử gán = rồi đến thực thể của đối tượng lớp. Bạn có thể xem cú pháp trong sơ đồ này:

f58430542f2081a9.png

Tạo thực thể lớp SmartDevice dưới dạng một đối tượng:

  • Trong hàm main(), hãy dùng từ khóa val để tạo một biến có tên smartTvDevice và khởi tạo biến đó dưới dạng một bản sao của lớp SmartDevice:
fun main() {
    val smartTvDevice = SmartDevice()
}

4. Xác định các phương thức của lớp

Trong Bài 1, bạn đã được học về:

  • Định nghĩa về hàm sử dụng từ khoá fun theo sau là cặp dấu ngoặc đơn và dấu ngoặc nhọn. Dấu ngoặc nhọn chứa mã, là hướng dẫn cần thiết để thực thi một tác vụ.
  • Việc gọi một hàm có thể khiến mã chứa trong hàm đó thực thi.

Các thao tác lớp có thể thực hiện được xác định là các hàm trong lớp. Ví dụ: hãy tưởng tượng bạn sở hữu một thiết bị thông minh, TV thông minh hoặc đèn thông minh và bạn có thể bật/tắt thiết bị đó bằng điện thoại di động. Thiết bị thông minh này được chuyển thành lớp SmartDevice trong quá trình lập trình. Thao tác bật/tắt thiết bị này sẽ được biểu thị bằng hàm turnOn() và hàm turnOff(), cho phép kích hoạt hành vi bật/tắt đó.

Cú pháp để xác định một hàm trong lớp giống hệt với cú pháp bạn đã học trước đó. Điểm khác biệt duy nhất là hàm được đặt trong phần nội dung của lớp. Khi bạn xác định một hàm trong phần nội dung lớp, hàm đó được gọi là hàm thành phần hoặc phương thức và đại diện cho hành vi của lớp. Trong phần còn lại của lớp học lập trình này, các hàm được gọi là phương thức bất cứ khi nào chúng xuất hiện trong phần nội dung của một lớp.

Xác định phương thức turnOn()turnOff() trong lớp SmartDevice:

  1. Trong phần nội dung của lớp SmartDevice, hãy xác định một phương thức turnOn() có phần nội dung trống:
class SmartDevice {
    fun turnOn() {

    }
}
  1. Trong phần nội dung của phương thức turnOn(), hãy thêm một câu lệnh println() rồi truyền câu lệnh đó thành một chuỗi "Smart device is turned on.":
class SmartDevice {
    fun turnOn() {
        println("Smart device is turned on.")
    }
}
  1. Sau phương thức turnOn(), hãy thêm một phương thức turnOff() để in chuỗi "Smart device is turned off.":
class SmartDevice {
    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}

Gọi một phương thức trên một đối tượng

Cho đến phần này, bạn đã xác định một lớp đóng vai trò là bản thiết kế cho một thiết bị thông minh, tạo một thực thể của lớp và gán thực thể đó cho một biến. Giờ đây, bạn có thể dùng phương thức của lớp SmartDevice để bật và tắt thiết bị.

Lệnh gọi một phương thức trong một lớp tương tự như cách bạn gọi các hàm khác từ hàm main() trong lớp học lập trình trước đó. Ví dụ: nếu cần gọi phương thức turnOff() từ phương thức turnOn(), bạn có thể viết nội dung tương tự như đoạn mã này:

class SmartDevice {
    fun turnOn() {
        // A valid use case to call the turnOff() method could be to turn off the TV when available power doesn't meet the requirement.
        turnOff()
        ...
    }

    ...
}

Để gọi một phương thức lớp bên ngoài lớp, hãy bắt đầu bằng đối tượng lớp theo sau là toán tử ., tên hàm và một cặp dấu ngoặc đơn. Nếu có thể, dấu ngoặc đơn có chứa các đối số mà phương thức yêu cầu. Bạn có thể xem cú pháp trong sơ đồ này:

fc609c15952551ce.png

Gọi phương thức turnOn()turnOff() trên đối tượng:

  1. Trong hàm main() trên dòng ở sau biến smartTvDevice, hãy gọi phương thức turnOn():
fun main() {
    val smartTvDevice = SmartDevice()
    smartTvDevice.turnOn()
}
  1. Trên dòng ở sau phương thức turnOn(), hãy gọi phương thức turnOff():
fun main() {
    val smartTvDevice = SmartDevice()
    smartTvDevice.turnOn()
    smartTvDevice.turnOff()
}
  1. Chạy mã.

Kết quả sẽ như sau:

Smart device is turned on.
Smart device is turned off.

5. Xác định các thuộc tính của lớp

Trong Bài 1, bạn đã học về các biến, là các vùng chứa cho một phần dữ liệu. Bạn đã tìm hiểu cách tạo một biến chỉ có thể đọc bằng từ khoá val và biến có thể thay đổi bằng từ khoá var.

Trong khi phương thức xác định các thao tác một lớp có thể thực hiện, thì các thuộc tính này xác định các đặc điểm hoặc thuộc tính dữ liệu của lớp. Ví dụ: một thiết bị thông minh có các thuộc tính sau:

  • Tên Tên thiết bị.
  • Loại thiết bị. Loại thiết bị thông minh, chẳng hạn như giải trí, tiện ích hoặc nấu ăn.
  • Trạng thái thiết bị. Cho dù thiết bị đang bật, tắt, trực tuyến hay ngoại tuyến. Thiết bị được coi là trực tuyến khi kết nối với Internet. Nếu không, nó sẽ được xem là ngoại tuyến.

Về cơ bản, thuộc tính là các biến được xác định trong phần nội dung lớp thay vì phần nội dung hàm. Điều này có nghĩa là cú pháp để xác định thuộc tính và biến giống hệt nhau. Bạn xác định một thuộc tính không thể thay đổi bằng từ khoá val và thuộc tính có thể thay đổi bằng từ khoá var.

Triển khai các đặc điểm nói trên dưới dạng thuộc tính của lớp SmartDevice:

  1. Trên dòng trước phương thức turnOn(), hãy xác định thuộc tính name và gán thuộc tính này cho một chuỗi "Android TV":
class SmartDevice {

    val name = "Android TV"

    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}
  1. Trên dòng sau thuộc tính name, hãy xác định thuộc tính category và chỉ định thuộc tính đó cho chuỗi "Entertainment", sau đó xác định thuộc tính deviceStatus và chỉ định thuộc tính đó cho chuỗi "online":
class SmartDevice {

    val name = "Android TV"
    val category = "Entertainment"
    var deviceStatus = "online"

    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}
  1. Trên dòng sau biến smartTvDevice, hãy gọi hàm println() rồi truyền hàm này vào một chuỗi "Device name is: ${smartTvDevice.name}":
fun main() {
    val smartTvDevice = SmartDevice()
    println("Device name is: ${smartTvDevice.name}")
    smartTvDevice.turnOn()
    smartTvDevice.turnOff()
}
  1. Chạy mã.

Kết quả sẽ như sau:

Device name is: Android TV
Smart device is turned on.
Smart device is turned off.

Hàm getter và setter trong các thuộc tính

Thuộc tính có thể làm được nhiều việc hơn một biến. Ví dụ: giả sử bạn tạo một cấu trúc lớp để đại diện cho một TV thông minh. Một trong những hành động phổ biến bạn thực hiện là tăng và giảm âm lượng. Để thể hiện hành động này trong lập trình, bạn có thể tạo một thuộc tính có tên là speakerVolume. Thuộc tính này giữ mức âm lượng hiện tại được đặt trên loa TV, nhưng có phạm vi giá trị cho âm lượng. Âm lượng tối thiểu có thể đặt là 0 và tối đa là 100. Để đảm bảo thuộc tính speakerVolume không bao giờ vượt quá 100 hoặc thấp hơn 0, bạn có thể viết một hàm setter. Khi cập nhật giá trị của thuộc tính, bạn cần kiểm tra xem giá trị đó có nằm trong khoảng từ 0 đến 100 hay không. Một ví dụ khác, hãy tưởng tượng có một yêu cầu để đảm bảo tên luôn phải viết hoa. Bạn có thể triển khai hàm getter để chuyển đổi thuộc tính name thành chữ viết hoa.

Trước khi tìm hiểu sâu hơn về cách triển khai các thuộc tính này, bạn cần hiểu rõ cú pháp để khai báo thuộc tính. Cú pháp đầy đủ để xác định thuộc tính có thể thay đổi bắt đầu bằng định nghĩa biến, theo sau là các hàm get()set() (không bắt buộc). Bạn có thể xem cú pháp trong sơ đồ này:

f2cf50a63485599f.png

Khi bạn không xác định hàm getter và setter cho một thuộc tính, trình biên dịch Kotlin sẽ tạo các hàm đó trong nội bộ. Ví dụ: nếu bạn sử dụng từ khoá var để xác định một thuộc tính speakerVolume và gán một giá trị 2 cho thuộc tính đó, trình biên dịch sẽ tự động tạo các hàm getter và setter như có thể thấy trong đoạn mã dưới đây:

var speakerVolume = 2
    get() = field
    set(value) {
        field = value
    }

Bạn sẽ không thấy những dòng này trong mã vì trình biên dịch đã thêm chúng vào nền.

Cú pháp đầy đủ cho thuộc tính không thể thay đổi có hai điểm khác biệt:

  • Nó bắt đầu với từ khóa val.
  • Các biến của val là các loại chỉ được đọc, do đó, chúng không có hàm set().

Thuộc tính Kotlin sử dụng trường sao lưu để lưu giữ giá trị trong bộ nhớ. Trường sao lưu về cơ bản là một biến lớp được xác định nội bộ bên trong các thuộc tính. Trường sao lưu nằm trong phạm vi một thuộc tính, có nghĩa là bạn chỉ có thể truy cập vào trường đó thông qua các hàm thuộc tính get() hoặc set().

Để đọc giá trị thuộc tính trong hàm get() hoặc cập nhật giá trị trong hàm set(), bạn cần sử dụng trường sao lưu của thuộc tính. Tệp này được trình biên dịch Kotlin tạo tự động và được tham chiếu bằng giá trị nhận dạng field.

Ví dụ: khi muốn cập nhật giá trị của thuộc tính trong hàm set(), bạn sử dụng tham số của hàm set() (còn được gọi là tham số value) và gán giá trị đó cho field biến như bạn có thể thấy trong đoạn mã này:

var speakerVolume = 2
    set(value) {
        field = value
    }

Ví dụ: để đảm bảo giá trị được gán cho thuộc tính speakerVolume nằm trong khoảng từ 0 đến 100, bạn có thể triển khai hàm setter như có thể thấy trong đoạn mã dưới đây:

var speakerVolume = 2
    set(value) {
        if (value in 0..100) {
            field = value
        }
    }

Hàm set() kiểm tra xem giá trị Int có nằm trong phạm vi từ 0 đến 100 hay không bằng cách dùng từ khoá in theo sau là phạm vi giá trị. Nếu giá trị nằm trong phạm vi dự kiến, thì giá trị field sẽ được cập nhật. Nếu không, giá trị của thuộc tính sẽ không thay đổi.

Bạn sẽ thêm thuộc tính này vào một lớp trong phần Triển khai mối quan hệ giữa các lớp của lớp học lập trình này, vì vậy, hiện tại bạn chưa cần phải thêm hàm setter vào mã.

6. Xác định hàm khởi tạo

Mục đích chính của hàm khởi tạo là chỉ định cách các đối tượng của lớp được tạo. Nói cách khác, hàm khởi tạo sẽ tạo một đối tượng và khiến đối tượng đó sẵn sàng để sử dụng. Bạn làm việc này khi tạo thực thể cho đối tượng. Mã bên trong hàm khởi tạo thực thi khi đối tượng của lớp được tạo thực thể. Bạn có thể xác định một hàm khởi tạo có hoặc không có tham số.

Hàm khởi tạo mặc định

Hàm khởi tạo mặc định là hàm khởi tạo không có tham số. Bạn có thể xác định một hàm khởi tạo mặc định như trong đoạn mã này:

class SmartDevice constructor() {
    ...
}

Kotlin luôn hướng đến sự súc tích, vì vậy bạn có thể xoá từ khoá constructor nếu không có chú thích hoặc công cụ sửa đổi chế độ hiển thị (bạn sẽ sớm được tìm hiểu về nó) trên hàm khởi tạo. Bạn cũng có thể xoá dấu ngoặc đơn nếu hàm khởi tạo không có tham số như minh hoạ trong đoạn mã dưới đây:

class SmartDevice {
    ...
}

Trình biên dịch Kotlin sẽ tự động tạo hàm khởi tạo mặc định. Bạn sẽ không thấy hàm khởi tạo mặc định được tạo tự động trong mã vì hàm đó đã được trình biên dịch thêm vào trong nền.

Xác định một hàm khởi tạo có tham số

Trong lớp SmartDevice, các thuộc tính namecategory là không thể thay đổi. Bạn cần đảm bảo tất cả các thực thể của lớp SmartDevice đều khởi tạo các thuộc tính namecategory. Bằng cách triển khai hiện tại, các giá trị của thuộc tính namecategory đã được mã hóa cứng. Điều này có nghĩa là tất cả thiết bị thông minh được đặt tên bằng chuỗi "Android TV" và được phân loại bằng chuỗi "Entertainment".

Để duy trì tính bất biến nhưng tránh các giá trị được mã hoá cứng, hãy dùng một hàm khởi tạo có tham số để tạo các giá trị đó:

  • Trong lớp SmartDevice, hãy di chuyển các thuộc tính namecategory sang hàm khởi tạo không phải chỉ định giá trị mặc định:
class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}

Hàm khởi tạo hiện chấp nhận các tham số để thiết lập thuộc tính, vì vậy, cách tạo thực thể của một đối tượng cho lớp đó cũng sẽ thay đổi. Bạn có thể xem cú pháp đầy đủ để tạo thực thể cho một đối tượng trong sơ đồ này:

bbe674861ec370b6.png

Dưới đây là nội dung biểu thị dòng mã:

SmartDevice("Android TV", "Entertainment")

Cả hai đối số cho hàm khởi tạo đều là chuỗi. Nó không rõ ràng về giá trị cần chỉ định cho tham số. Để khắc phục lỗi này, tương tự như cách bạn chuyển các đối số của hàm, bạn có thể tạo một hàm khởi tạo có đối số được đặt tên như minh hoạ trong đoạn mã dưới đây:

SmartDevice(name = "Android TV", category = "Entertainment")

Có hai loại hàm khởi tạo chính trong Kotlin:

  • Hàm khởi tạo chính. Một lớp chỉ có thể có một hàm khởi tạo chính, được xác định là một phần của tiêu đề lớp. Một hàm khởi tạo chính có thể là một hàm khởi tạo mặc định hoặc có tham số. Hàm khởi tạo chính không có nội dung. Điều đó có nghĩa là nó không được chứa bất kỳ mã nào.
  • Hàm khởi tạo phụ. Một lớp có thể có nhiều hàm khởi tạo phụ. Bạn có thể xác định hàm khởi tạo phụ có hoặc không có tham số. Hàm khởi tạo phụ có thể khởi tạo lớp và có một phần nội dung có thể chứa logic khởi tạo. Nếu lớp có một hàm khởi tạo chính, mỗi hàm khởi tạo phụ đều phải tạo hàm khởi tạo chính.

Bạn có thể dùng hàm khởi tạo chính để tạo các thuộc tính trong tiêu đề lớp. Các đối số được chuyển đến hàm khởi tạo được gán cho các thuộc tính. Cú pháp để xác định một hàm khởi tạo chính bắt đầu bằng tên lớp, theo sau là từ khoá constructor và một cặp dấu ngoặc đơn. Dấu ngoặc đơn chứa các tham số cho hàm khởi tạo chính. Nếu có nhiều hơn một tham số, dấu phẩy sẽ phân tách các định nghĩa tham số. Bạn có thể xem cú pháp đầy đủ để xác định một hàm khởi tạo chính trong sơ đồ này:

aa05214860533041.png

Hàm khởi tạo phụ nằm trong phần nội dung của lớp và cú pháp của hàm này bao gồm 3 phần:

  • Khai báo hàm khởi tạo phụ. Việc xác định hàm khởi tạo phụ bắt đầu bằng từ khoá constructor theo sau là dấu ngoặc đơn. Nếu thích hợp, dấu ngoặc đơn chứa các tham số mà hàm khởi tạo phụ yêu cầu.
  • Khởi tạo hàm khởi tạo chính. Khởi tạo bằng dấu hai chấm, theo sau là từ khoá this và một cặp dấu ngoặc đơn. Nếu thích hợp, dấu ngoặc đơn sẽ chứa các tham số hàm khởi tạo chính yêu cầu.
  • Nội dung hàm khởi tạo phụ. Khởi tạo hàm khởi tạo chính, theo sau là một cặp dấu ngoặc nhọn, chứa phần nội dung của hàm khởi tạo phụ.

Bạn có thể xem cú pháp trong sơ đồ này:

2dc13ef136009e98.png

Ví dụ: giả sử bạn muốn tích hợp API do một nhà cung cấp thiết bị thông minh phát triển. Tuy nhiên, API sẽ trả về mã trạng thái của loại Int để cho biết trạng thái thiết bị ban đầu. API trả về một giá trị 0 nếu thiết bị ngoại tuyến và một giá trị 1 nếu thiết bị đó trực tuyến. Đối với bất kỳ giá trị số nguyên nào khác, trạng thái được coi là không xác định. Bạn có thể tạo một hàm khởi tạo phụ trong lớp SmartDevice để chuyển đổi tham số statusCode này thành giá trị biểu diễn dạng chuỗi như có thể thấy trong đoạn mã dưới đây:

class SmartDevice(val name: String, val category: String) {
    var deviceStatus = "online"

    constructor(name: String, category: String, statusCode: Int) : this(name, category) {
        deviceStatus = when (statusCode) {
            0 -> "offline"
            1 -> "online"
            else -> "unknown"
        }
    }
    ...
}

7. Triển khai mối quan hệ giữa các lớp

Tính kế thừa cho phép bạn tạo một lớp dựa trên đặc điểm và hành vi của một lớp khác. Đây là một cơ chế hiệu quả giúp bạn viết mã có thể sử dụng lại và thiết lập mối quan hệ giữa các lớp.

Ví dụ: có nhiều thiết bị thông minh trên thị trường, chẳng hạn như TV thông minh, đèn thông minh và công tắc thông minh. Khi bạn biểu diễn các thiết bị thông minh trong lập trình, chúng sẽ dùng chung một số thuộc tính như tên, danh mục và trạng thái. Chúng cũng có các hành vi phổ biến, chẳng hạn như khả năng bật và tắt những tuỳ chọn đó.

Tuy nhiên, cách bật hoặc tắt từng thiết bị thông minh sẽ khác nhau. Ví dụ: để bật TV, bạn có thể phải bật màn hình, sau đó thiết lập mức âm lượng và kênh đã mở lần gần nhất. Mặt khác, để bật đèn, bạn có thể chỉ cần tăng hoặc giảm độ sáng.

Ngoài ra, mỗi thiết bị thông minh có nhiều chức năng và thao tác thực hiện hơn. Ví dụ: đối với TV, bạn có thể điều chỉnh âm lượng và thay đổi kênh. Với đèn, bạn có thể điều chỉnh độ sáng hoặc màu sắc.

Tóm lại, tất cả các thiết bị thông minh đều có các tính năng khác nhau, nhưng có chung một số đặc điểm. Bạn có thể sao chép các đặc điểm chung này tới từng lớp thiết bị thông minh hoặc làm cho mã có thể tái sử dụng với tính kế thừa.

Để làm việc này, bạn cần tạo một lớp mẹ SmartDevice và xác định các thuộc tính cũng như hành vi phổ biến này. Sau đó, bạn có thể tạo các lớp con, chẳng hạn như lớp SmartTvDeviceSmartLightDevice. Các lớp này kế thừa các thuộc tính của lớp cha.

Trong các ngôn ngữ lập trình, chúng ta nói lớp SmartTvDeviceSmartLightDevice mở rộng lớp mẹ SmartDevice. Lớp mẹ còn được gọi là lớp cấp cao và lớp con là lớp phụ. Bạn có thể thấy mối quan hệ giữa chúng trong sơ đồ này:

Sơ đồ thể hiện mối quan hệ kế thừa giữa các lớp.

Tuy nhiên, trong Kotlin, theo mặc định, tất cả các lớp đều là cuối cùng, nghĩa là bạn không thể mở rộng chúng, vì vậy bạn phải xác định mối quan hệ giữa các lớp.

Xác định mối quan hệ giữa lớp cấp cao SmartDevice và các lớp con của chúng:

  1. Trong lớp cấp cao SmartDevice, hãy thêm từ khoá open trước từ khoá class để có thể mở rộng từ khoá này:
open class SmartDevice(val name: String, val category: String) {
    ...
}

Từ khoá open thông báo cho trình biên dịch rằng lớp này có thể mở rộng. Nhờ đó, vào lúc này lớp khác sẽ có thể mở rộng lớp này.

Cú pháp để tạo lớp con bắt đầu bằng việc tạo tiêu đề lớp như bạn đã làm từ trước đến nay. Sau dấu ngoặc đơn đóng của hàm khởi tạo là dấu cách, dấu hai chấm, dấu cách khác, tên lớp cấp cao và một cặp dấu ngoặc đơn. Nếu cần, dấu ngoặc đơn sẽ bao gồm các tham số mà hàm khởi tạo lớp cấp cao yêu cầu. Bạn có thể xem cú pháp trong sơ đồ này:

1ac63b66e6b5c224.png

  1. Tạo lớp con SmartTvDevice mở rộng lớp cấp cao SmartDevice:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {
}

Việc xác định constructor cho SmartTvDevice không chỉ định xem các thuộc tính này có thể thay đổi hay không thể thay đổi. Điều này có nghĩa là tham số deviceNamedeviceCategory chỉ là tham số constructor hơn là thuộc tính lớp. Bạn sẽ không thể dùng chúng trong lớp này, chỉ cần chuyển các lớp đó đến hàm khởi tạo lớp cấp cao.

  1. Trong phần nội dung của lớp con SmartTvDevice, hãy thêm thuộc tính speakerVolume bạn đã tạo khi được học về các hàm getter và setter:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }
}
  1. Xác định một thuộc tính channelNumber được chỉ định cho giá trị 1 bằng một hàm setter chỉ định một phạm vi 0..200:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }
}
  1. Xác định phương thức increaseSpeakerVolume() tăng âm lượng và in chuỗi "Speaker volume increased to $speakerVolume.":
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

     var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }
}
  1. Thêm phương thức nextChannel() để tăng số lượng kênh và in chuỗi "Channel number increased to $channelNumber.":
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }
}
  1. Trên dòng sau lớp con SmartTvDevice, hãy xác định lớp con SmartLightDevice mở rộng lớp cấp cao SmartDevice:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {
}
  1. Trong phần nội dung lớp con SmartLightDevice, hãy xác định một thuộc tính brightnessLevel được gán cho giá trị 0 bằng một hàm setter chỉ định một phạm vi 0..100:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }
}
  1. Xác định phương thức increaseBrightness() tăng độ sáng của đèn và in chuỗi "Brightness increased to $brightnessLevel.":
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }
}

Mối quan hệ giữa các lớp

Khi sử dụng tính kế thừa, bạn thiết lập mối quan hệ giữa hai lớp trong mối quan hệ IS-A. Đối tượng cũng là một thực thể của lớp, từ đó đối tượng này kế thừa. Trong mối quan hệ HAS-A, một đối tượng có thể sở hữu một thực thể của lớp khác mà không thực sự là thực thể của lớp đó. Bạn có thể thấy phần biểu thị cấp cao về các mối quan hệ trong sơ đồ dưới đây:

Phần biểu thị cấp cao của mối quan hệ của HAS-A và IS-A.

Mối quan hệ IS-A

Khi bạn chỉ định mối quan hệ IS-A giữa lớp cấp cao SmartDevice và lớp con SmartTvDevice, điều đó có nghĩa là lớp con SmartTvDevice có thể làm bất cứ điều gì mà lớp cấp cao SmartDevice làm được. Mối quan hệ này là một chiều, vì vậy mỗi TV thông minh là một thiết bị thông minh nhưng mỗi thiết bị thông minh không hẳn đều TV thông minh. Mã biểu thị cho mối quan hệ IS-A được minh hoạ trong đoạn mã dưới đây:

// Smart TV IS-A smart device.
class SmartTvDevice : SmartDevice() {
}

Không sử dụng tính kế thừa chỉ để đạt được tính tái sử dụng mã. Trước khi bạn quyết định, hãy kiểm tra xem hai lớp có liên quan với nhau hay không. Nếu chúng có thể hiện một số mối quan hệ, hãy kiểm tra xem chúng có thực sự đủ điều kiện để thiết lập mối quan hệ IS-A hay không. Hãy tự hỏi: "Lớp con có phải là lớp cấp cao không?". Ví dụ: Android một hệ điều hành.

Mối quan hệ HAS-A

Mối quan hệ HAS-A là một cách khác để chỉ định mối quan hệ giữa hai lớp. Ví dụ: bạn có thể đang sử dụng TV thông minh tại nhà của mình. Trong trường hợp này, có mối quan hệ giữa TV thông minh và nhà bạn. Nhà này có chứa một thiết bị thông minh, hoặc nói cách khác là nhà có mộtthiết bị thông minh. Mối quan hệ HAS-A giữa hai lớp cũng được gọi là sự kết hợp.

Cho đến nay, bạn đã tạo được một vài thiết bị thông minh. Bây giờ, bạn sẽ tạo lớp SmartHome, chứa các thiết bị thông minh. Lớp SmartHome cho phép bạn tương tác với các thiết bị thông minh.

Sử dụng mối quan hệ HAS-A để xác định lớp SmartHome:

  1. Ở giữa lớp SmartLightDevice và hàm main(), hãy xác định một lớp SmartHome:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    ...

}

class SmartHome {
}

fun main() {
    ...
}
  1. Trong hàm khởi tạo lớp SmartHome, hãy dùng từ khóa val để tạo thuộc tính smartTvDevice thuộc loại SmartTvDevice:
// The SmartHome class HAS-A smart TV device.
class SmartHome(val smartTvDevice: SmartTvDevice) {

}
  1. Trong phần nội dung của lớp SmartHome, hãy xác định một phương thức turnOnTv() để gọi phương thức turnOn() trên thuộc tính smartTvDevice:
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }
}
  1. Trên dòng sau phương thức turnOnTv(), hãy xác định phương thức turnOffTv() để gọi phương thức turnOff() trên thuộc tính smartTvDevice:
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        smartTvDevice.turnOff()
    }

}
  1. Trên dòng sau phương thức turnOffTv(), hãy xác định phương thức increaseTvVolume() để gọi phương thức increaseSpeakerVolume() trên thuộc tính smartTvDevice, sau đó xác định phương thức changeTvChannelToNext() để gọi phương thức nextChannel() trên thuộc tính smartTvDevice:
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        smartTvDevice.turnOff()
    }

    fun increaseTvVolume() {
        smartTvDevice.increaseSpeakerVolume()
    }

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }
}
  1. Trong hàm khởi tạo lớp SmartHome, di chuyển tham số thuộc tính smartTvDevice sang dòng riêng, theo sau là dấu phẩy:
class SmartHome(
    val smartTvDevice: SmartTvDevice,
) {

    ...

}
  1. Trên dòng sau thuộc tính smartTvDevice, dùng từ khóa val để xác định thuộc tính smartLightDevice thuộc loại SmartLightDevice:
// The SmartHome class HAS-A smart TV device and smart light.
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

}
  1. Trong SmartHome nội dung, xác định phương thứcturnOnLight() để gọi phương thức turnOn() trên đối tượng smartLightDevice và một đối tượngturnOffLight() để gọi phương thức turnOff() trên đối tượng smartLightDevice:
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }

    fun turnOnLight() {
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        smartLightDevice.turnOff()
    }
}
  1. Trên dòng sau phương thức turnOffLight(), hãy xác định phương thức increaseLightBrightness() để gọi phương thức increaseBrightness() trên thuộc tính smartLightDevice:
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }

    fun turnOnLight() {
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        smartLightDevice.turnOff()
    }

    fun increaseLightBrightness() {
        smartLightDevice.increaseBrightness()
    }
}
  1. Trên dòng sau phương thức increaseLightBrightness(), hãy xác định phương thức turnOffAllDevices() để gọi phương thức turnOffTv()turnOffLight():.
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

    fun turnOffAllDevices() {
        turnOffTv()
        turnOffLight()
    }
}

Ghi đè phương thức của lớp cấp cao từ các lớp con

Như đã thảo luận trước đó, mặc dù chức năng bật và tắt được tất cả các thiết bị thông minh hỗ trợ, nhưng cách thực hiện chức năng này lại khác nhau. Để cung cấp hành vi dành riêng cho thiết bị này, bạn cần ghi đè phương thức turnOn()turnOff() được xác định trong lớp cấp cao. Để ghi đè nghĩa là chặn hành động, thường là để kiểm soát thủ công. Khi bạn ghi đè một phương thức, phương thức trong lớp con sẽ làm gián đoạn quá trình thực thi phương thức được xác định trong lớp cấp cao và cung cấp phương thức thực thi của chính nó.

Ghi đè lớp SmartDevice phương thức turnOn()turnOff():

  1. Trong phần nội dung của lớp cấp cao SmartDevice trước từ khoá fun của mỗi phương thức, hãy thêm từ khoá open:
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    open fun turnOn() {
        // function body
    }

    open fun turnOff() {
        // function body
    }
}
  1. Trong phần nội dung của lớp SmartLightDevice, hãy xác định một phương thức turnOn() có phần nội dung trống:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    fun turnOn() {
    }
}
  1. Trong phần nội dung của phương thức turnOn(), thiết lập thuộc tính deviceStatus thành chuỗi "on", thiết lập thuộc tính brightnessLevel thành một giá trị 2, rồi thêm câu lệnh println() sau đó truyền vào câu lệnh đó chuỗi "$name turned on. The brightness level is $brightnessLevel.":
    fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }
  1. Trong phần nội dung của lớp SmartLightDevice, hãy xác định một phương thức turnOff() có phần nội dung trống:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    fun turnOff() {
    }
}
  1. Trong phần nội dung của phương thức turnOff(), thiết lập thuộc tính deviceStatus thành chuỗi "off", thiết lập thuộc tính brightnessLevel thành một giá trị 0 rồi thêm câu lệnh println() sau đó truyền vào câu lệnh đó chuỗi "Smart Light turned off":
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    fun turnOff() {
        deviceStatus = "off"
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}
  1. Trong lớp con SmartLightDevice trước từ khoá fun của phương thức turnOn()turnOff(), hãy thêm từ khoá override:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    override fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    override fun turnOff() {
        deviceStatus = "off"
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}

Từ khoá override thông báo cho Kotlin trong thời gian chạy để thực thi mã có trong phương thức đã xác định trong lớp con.

  1. Trong phần nội dung của lớp SmartTvDevice, hãy xác định một phương thức turnOn() có phần nội dung trống:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    fun turnOn() {
    }
}
  1. Trong phần nội dung của phương thức turnOn(), hãy thiết lập thuộc tính deviceStatus thành chuỗi "on" rồi thêm một câu lệnh println(), sau đó truyền vào đó chuỗi "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " + "set to $channelNumber.":
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    fun turnOn() {
        deviceStatus = "on"
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }
}
  1. Trong phần nội dung của lớp SmartTvDevice sau phương thức turnOn(), hãy xác định phương thức turnOff() có phần nội dung trống:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    fun turnOn() {
        ...
    }

    fun turnOff() {
    }
}
  1. Trong phần nội dung của phương thức turnOff(), hãy thiết lập thuộc tính deviceStatus thành chuỗi "off" rồi thêm câu lệnh println(), sau đó truyền vào đó chuỗi "$name turned off":
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    fun turnOn() {
        ...
    }

    fun turnOff() {
        deviceStatus = "off"
        println("$name turned off")
    }
}
  1. Trong lớp SmartTvDevice trước từ khoá fun của phương thức turnOn()turnOff(), hãy thêm từ khoá override:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    override fun turnOn() {
        deviceStatus = "on"
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }

    override fun turnOff() {
        deviceStatus = "off"
        println("$name turned off")
    }
}
  1. Trong hàm main(), hãy dùng từ khoá var để xác định biến smartDevice thuộc loại SmartDevice sẽ tạo thực thể cho đối tượng SmartTvDevice để lấy đối số "Android TV" và đối số "Entertainment":
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
}
  1. Trên dòng sau biến smartDevice, gọi phương thức turnOn() trên đối tượng smartDevice:
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
    smartDevice.turnOn()
}
  1. Chạy mã.

Kết quả sẽ như sau:

Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
  1. Trên dòng sau khi gọi phương thức turnOn(), chỉ định lại biến smartDevice để tạo thực thể cho một lớp SmartLightDevice nhận đối số "Google Light" và một đối số "Utility", và sau đó gọi phương thức turnOn() trên tham chiếu đối tượng smartDevice:
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
    smartDevice.turnOn()

    smartDevice = SmartLightDevice("Google Light", "Utility")
    smartDevice.turnOn()
}
  1. Chạy mã.

Kết quả sẽ như sau:

Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
Google Light turned on. The brightness level is 2.

Đây là một ví dụ về tính đa hình. Mã này gọi phương thức turnOn() trên biến thuộc loại SmartDevice và tuỳ thuộc vào giá trị thực tế của biến đó, bạn có thể thực thi các quy trình triển khai khác nhau của phương thức turnOn().

Sử dụng lại mã lớp cấp cao trong các lớp con bằng từ khoá super

Khi xem xét kỹ phương thức turnOn()turnOff(), bạn nhận thấy biến deviceStatus có điểm giống nhau trong cách nó được cập nhật, bất cứ khi nào phương thức được gọi trong lớp con SmartTvDeviceSmartLightDevice: mã bị trùng lặp. Bạn có thể sử dụng lại mã khi cập nhật trạng thái trong lớp SmartDevice.

Để gọi phương thức bị ghi đè trong lớp cấp cao từ lớp con, bạn cần sử dụng từ khoá super. Việc gọi một phương thức từ lớp cấp cao cũng tương tự như gọi phương thức từ bên ngoài lớp. Thay vì dùng toán tử . giữa đối tượng và phương thức, bạn cần dùng từ khoá super để thông báo cho trình biên dịch Kotlin gọi phương thức trên lớp cấp cao thay vì lớp con.

Cú pháp để gọi phương thức từ lớp cấp cao bắt đầu bằng từ khoá super, theo sau là toán tử ., tên hàm và một cặp dấu ngoặc đơn. Nếu có thể, dấu ngoặc đơn có bao gồm các đối số. Bạn có thể xem cú pháp trong sơ đồ này:

18cc94fefe9851e0.png

Sử dụng lại mã trong lớp cấp cao SmartDevice:

  1. Xoá các câu lệnh println() khỏi phương thức turnOn()turnOff(), đồng thời di chuyển mã trùng lặp từ lớp con SmartTvDeviceSmartLightDevice sang lớp cấp cao SmartDevice:
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    open fun turnOn() {
        deviceStatus = "on"
    }

    open fun turnOff() {
        deviceStatus = "off"
    }
}
  1. Dùng từ khoá super để gọi các phương thức từ lớp SmartDevice trong các lớp con SmartTvDeviceSmartLightDevice:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

     var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    override fun turnOn() {
        super.turnOn()
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }

    override fun turnOff() {
        super.turnOff()
        println("$name turned off")
    }
}
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    override fun turnOn() {
        super.turnOn()
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    override fun turnOff() {
        super.turnOff()
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}

Ghi đè các thuộc tính lớp cấp cao từ lớp con

Tương tự như phương thức, bạn cũng có thể ghi đè các thuộc tính theo các bước tương tự.

Ghi đè thuộc tính deviceType:

  1. Trong lớp cấp cao SmartDevice trên dòng sau thuộc tính deviceStatus, hãy dùng từ khoá openval để xác định thuộc tính deviceType được đặt thành chuỗi "unknown":
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    open val deviceType = "unknown"
    ...
}
  1. Trong lớp SmartTvDevice, hãy dùng từ khoá overrideval để xác định thuộc tính deviceType được đặt thành chuỗi "Smart TV":
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    ...
}
  1. Trong lớp SmartLightDevice, hãy dùng từ khoá overrideval để xác định thuộc tính deviceType được đặt thành chuỗi "Smart Light":
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart Light"

    ...

}

8. Đối tượng sửa đổi chế độ hiển thị

Hệ số sửa đổi chế độ hiển thị đóng vai trò quan trọng để hoàn thiện đóng gói:

  • Trong lớp, nó cho phép bạn ẩn các thuộc tính và phương thức khỏi quyền truy cập trái phép từ bên ngoài lớp.
  • Trong gói, nó cho phép bạn ẩn các lớp và giao diện khỏi quyền truy cập trái phép từ bên ngoài gói.

Kotlin cung cấp 4 hệ số sửa đổi chế độ hiển thị:

  • Đối tượng sửa đổi chế độ hiển thị mặc địnhpublic.. Cho phép khai báo có thể truy cập ở mọi nơi. Các thuộc tính và phương thức bạn muốn dùng bên ngoài lớp sẽ được đánh dấu công khai.
  • private. Cho phép khai báo có thể truy cập trong cùng một lớp hoặc tệp nguồn.

Có một số thuộc tính và phương thức chỉ được dùng bên trong lớp đó và bạn không nhất thiết muốn các lớp khác sử dụng. Bạn có thể đánh dấu các thuộc tính và phương thức này bằng đối tượng sửa đổi chế độ hiển thị private để đảm bảo một lớp khác không thể vô tình truy cập vào.

  • protected. Cho phép khai báo có thể truy cập trong các lớp con. Các thuộc tính và phương thức bạn muốn dùng trong lớp xác định các lớp đó và lớp con được đánh dấu bằng đối tượng sửa đổi chế độ hiển thị protected.
  • internal. Cho phép khai báo có thể truy cập được trong cùng một mô-đun. Đối tượng sửa đổi nội bộ cũng giống như đối tượng sửa đổi riêng tư, nhưng bạn có thể truy cập vào các thuộc tính và phương thức nội bộ từ bên ngoài lớp, miễn là đối tượng này được truy cập trong cùng một mô-đun.

Khi bạn xác định một lớp, lớp này sẽ hiển thị công khai và bất kỳ gói nào nhập lớp đều có thể truy cập, tức là lớp đó ở chế độ công khai theo mặc định, trừ khi bạn chỉ định đối tượng sửa đổi chế độ hiển thị. Tương tự, khi bạn xác định hoặc khai báo các thuộc tính và phương thức trong lớp, theo mặc định, những thuộc tính và phương thức này có thể được truy cập bên ngoài lớp thông qua đối tượng lớp. Điều cần thiết là phải xác định chế độ hiển thị thích hợp cho mã, chủ yếu để ẩn các thuộc tính và phương thức các lớp khác không cần thiết truy cập.

Ví dụ: hãy xem cách người lái xe có thể sử dụng ô tô. Theo mặc định, những chi tiết bộ phận tạo nên một chiếc xe ô tô và cách xe hoạt động được ẩn giấu bên trong. Chiếc xe ô tô được thiết kế để vận hành trực quan nhất có thể. Bạn không muốn ô tô hoạt động phức tạp như một máy bay thương mại, tương tự như việc bạn không muốn một nhà phát triển khác hoặc bản thân bạn trong tương lai bị nhầm lẫn về cách sử dụng thuộc tính và phương pháp của một lớp.

Đối tượng sửa đổi chế độ hiển thị giúp bạn hiển thị các phần mã liên quan đến các lớp khác trong dự án và đảm bảo loại trừ việc triển khai một cách vô tình, điều này giúp mã dễ hiểu và ít xảy ra lỗi hơn.

Bạn phải đặt đối tượng sửa đổi chế độ hiển thị trước cú pháp khai báo, đồng thời khai báo lớp, phương thức hoặc thuộc tính như có thể thấy trong sơ đồ dưới đây:

dcc4f6693bf719a9.png

Xác định đối tượng sửa đổi chế độ hiển thị cho các thuộc tính

Cú pháp để xác định đối tượng sửa đổi chế độ hiển thị cho một thuộc tính bắt đầu bằng đối tượng sửa đổi private, protected hoặc internal, theo sau là cú pháp xác định thuộc tính. Bạn có thể xem cú pháp trong sơ đồ này:

47807a890d237744.png

Ví dụ: bạn có thể xem cách thiết lập thuộc tính deviceStatus thành riêng tư trong đoạn mã này:

open class SmartDevice(val name: String, val category: String) {

    ...

    private var deviceStatus = "online"

    ...
}

Bạn cũng có thể đặt hệ số sửa đổi chế độ hiển thị thành các hàm setter. Đối tượng sửa đổi được đặt trước từ khoá set. Bạn có thể xem cú pháp trong sơ đồ này:

cea29a49b7b26786.png

Đối với lớp SmartDevice, giá trị của thuộc tính deviceStatus phải đọc được bên ngoài lớp thông qua các đối tượng lớp. Tuy nhiên, chỉ có lớp và các lớp con mới có thể cập nhật hoặc ghi giá trị. Để triển khai yêu cầu này, bạn cần phải sử dụng đối tượng sửa đổi protected trên hàm set() của thuộc tính deviceStatus.

Dùng đối tượng sửa đổi protected trên hàm set() của thuộc tính deviceStatus:

  1. Trong thuộc tính deviceStatus của lớp cấp cao SmartDevice, thêm hệ số sửa đổi protected vào hàm set():
open class SmartDevice(val name: String, val category: String) {

    ...

    var deviceStatus = "online"
        protected set(value) {
           field = value
       }

    ...
}

Bạn hiện không thực hiện bất kỳ hành động hoặc bước kiểm tra nào trong hàm set(). Bạn chỉ cần chỉ định tham số value cho biến field. Như bạn đã được học, điều này tương tự như cách triển khai mặc định cho phương thức setter thuộc tính. Trong trường hợp này, bạn có thể bỏ qua dấu ngoặc đơn và nội dung của hàm set():

open class SmartDevice(val name: String, val category: String) {

    ...

    var deviceStatus = "online"
        protected set

    ...
}
  1. Trong lớp SmartHome, hãy xác định một thuộc tính deviceTurnOnCount được đặt thành giá trị 0 bằng một hàm setter riêng tư:
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    var deviceTurnOnCount = 0
        private set

    ...
}
  1. Thêm thuộc tính deviceTurnOnCount theo sau là toán tử số học ++ vào phương thức turnOnTv()turnOnLight(). Sau đó, thêm thuộc tính deviceTurnOnCount theo sau là toán tử số học -- vào phương thức turnOffTv()turnOffLight():
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    var deviceTurnOnCount = 0
        private set

    fun turnOnTv() {
        deviceTurnOnCount++
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        deviceTurnOnCount--
        smartTvDevice.turnOff()
    }

    ...

    fun turnOnLight() {
        deviceTurnOnCount++
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        deviceTurnOnCount--
        smartLightDevice.turnOff()
    }

    ...

}

Chỉ định truy cập cho các phương thức

Cú pháp để chỉ định một đối tượng sửa đổi mức độ hiển thị cho một phương thức bắt đầu bằng đối tượng sửa đổi private, protected hoặc internal, theo sau là cú pháp xác định một phương thức. Bạn có thể xem cú pháp trong sơ đồ này:

e0a60ddc26b841de.png

Ví dụ: bạn có thể xem cách chỉ định một đối tượng sửa đổi protected cho phương thức nextChannel() trong lớp SmartTvDevice trong đoạn mã này:

class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    protected fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    ...
}

Đối tượng sửa đổi chế độ hiển thị cho các hàm khởi tạo

Cú pháp để chỉ định đối tượng sửa đổi chế độ hiển thị cho hàm khởi tạo tương tự như cách xác định hàm khởi tạo chính với một vài điểm khác biệt:

  • Đối tượng sửa đổi được chỉ định sau tên lớp, nhưng trước từ khoá constructor.
  • Nếu cần chỉ định đối tượng sửa đổi cho hàm khởi tạo chính, bạn cần phải giữ từ khoá constructor và dấu ngoặc đơn ngay cả khi không có bất kỳ tham số nào.

Bạn có thể xem cú pháp trong sơ đồ này:

6832575eba67f059.png

Ví dụ: bạn có thể xem cách thêm đối tượng sửa đổi protected vào hàm khởi tạo SmartDevice trong đoạn mã dưới đây:

open class SmartDevice protected constructor (val name: String, val category: String) {

    ...

}

Đối tượng sửa đổi chế độ hiển thị cho các lớp

Cú pháp để chỉ định đối tượng sửa đổi chế độ hiển thị cho một lớp bắt đầu bằng đối tượng sửa đổi private, protected hoặc internal, theo sau là cú pháp xác định một lớp. Bạn có thể xem cú pháp trong sơ đồ này:

3ab4aa1c94a24a69.png

Ví dụ: bạn có thể xem cách chỉ định đối tượng sửa đổi internal cho lớp SmartDevice trong đoạn mã dưới đây:

internal open class SmartDevice(val name: String, val category: String) {

    ...

}

Tốt nhất là bạn nên cố gắng hiển thị nghiêm ngặt các thuộc tính và phương thức, vì vậy, hãy khai báo chúng bằng đối tượng sửa đổi private thường xuyên nhất có thể. Nếu bạn không thể đặt những email này ở chế độ riêng tư, hãy dùng hệ số sửa đổi protected. Nếu bạn không thể bảo vệ những dữ liệu đó, hãy dùng hệ số sửa đổi internal. Nếu bạn không thể giữ chúng trong nội bộ, hãy sử dụng hệ số sửa đổi public.

Chỉ định đối tượng sửa đổi chế độ hiển thị phù hợp

Bảng này giúp bạn xác định đối tượng sửa đổi chế độ hiển thị phù hợp dựa trên vị trí mà thuộc tính hay phương thức của một lớp hoặc hàm khởi tạo có thể truy cập được:

Ký tự bổ trợ

Có thể truy cập trong cùng một lớp

Có thể truy cập trong lớp con

Có thể truy cập được trong cùng mô-đun

Có thể truy cập ô-đun bên ngoài

private

𝗫

𝗫

𝗫

protected

𝗫

𝗫

internal

𝗫

public

Trong lớp con SmartTvDevice, bạn không nên cho phép kiểm soát các thuộc tính speakerVolumechannelNumber từ bên ngoài lớp. Bạn chỉ nên kiểm soát các thuộc tính này thông qua các phương thức increaseSpeakerVolume()nextChannel().

Tương tự, trong lớp con SmartLightDevice, thuộc tính brightnessLevel chỉ nên được kiểm soát thông qua phương thức increaseLightBrightness().

Thêm hệ số xác định mức độ hiển thị phù hợp vào các lớp con SmartTvDeviceSmartLightDevice:

  1. Trong lớp SmartTvDevice, hãy thêm một đối tượng sửa đổi chế độ hiển thị private vào các thuộc tính speakerVolumechannelNumber:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    private var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    private var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    ...
}
  1. Trong lớp SmartLightDevice, hãy thêm một đối tượng sửa đổi private vào thuộc tính brightnessLevel:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    private var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    ...
}

9. Xác định lớp uỷ quyền thuộc tính

Bạn đã tìm hiểu trong phần trước rằng các thuộc tính trong Kotlin sử dụng một trường sao lưu để lưu giữ giá trị của các thuộc tính đó trong bộ nhớ. Bạn sử dụng mã nhận dạng field để tham chiếu mã này.

Khi xem mã cho đến thời điểm, bạn có thể thấy nhân bản mã để kiểm tra xem liệu các giá trị có nằm trong phạm vi cho thuộc tínhspeakerVolume, channelNumberbrightnessLevel trong lớp SmartTvDeviceSmartLightDevice. Bạn có thể sử dụng lại mã kiểm tra phạm vi trong hàm setter bằng lệnh ủy quyền. Thay vì sử dụng một trường, cũng như một hàm getter và setter để quản lý giá trị, lớp uỷ quyền sẽ quản lý giá trị đó.

Cú pháp để tạo uỷ quyền thuộc tính bắt đầu bằng phần khai báo một biến theo sau là từ khoá by và đối tượng uỷ quyền xử lý các hàm setter và getter cho thuộc tính. Bạn có thể xem cú pháp trong sơ đồ này:

928547ad52768115.png

Trước khi triển khai lớp mà bạn có thể uỷ quyền để triển khai, bạn cần làm quen với giao diện. Giao diện là một "hợp đồng" mà các lớp triển khai nó này cần phải tuân thủ. Nó tập trung vào việc nên làm thay vì cách thực hiện hành động. Tóm lại, giao diện sẽ giúp bạn đạt được sự trừu tượng.

Ví dụ: trước khi xây nhà, bạn phải thông tin cho kiến trúc sư về những gì bạn muốn. Bạn muốn có phòng ngủ, phòng cho trẻ em, phòng khách, bếp và một vài phòng tắm. Một cách ngắn gọn, bạn xác định những điều bạn muốn và kiến trúc sư chỉ định cách đạt được mục tiêu này. Bạn có thể xem cú pháp để tạo giao diện trong sơ đồ này:

bfe3fd1cd8c45b2a.png

Bạn đã biết cách mở rộng lớp và ghi đè chức năng của lớp đó. Với các giao diện, lớp triển khai giao diện. Lớp cung cấp thông tin triển khai cho các phương thức và thuộc tính được khai báo trong giao diện. Bạn sẽ thực hiện một thao tác tương tự với giao diện ReadWriteProperty để tạo lớp uỷ quyền. Bạn sẽ tìm hiểu thêm về các giao diện trong bài sau.

Để tạo lớp ủy quyền cho loại var, bạn cần triển khai giao diện ReadWriteProperty. Tương tự, bạn cần triển khai giao diện ReadOnlyProperty cho loại val.

Tạo ủy quyền cho loại var:

  1. Trước hàm main(), tạo một lớp RangeRegulator sẽ triển khai giao diện ReadWriteProperty<Any?, Int>:
class RangeRegulator() : ReadWriteProperty<Any?, Int> {

}

fun main() {
    ...
}

Đừng lo lắng về dấu ngoặc nhọn hoặc nội dung bên trong nó. Chúng biểu thị các loại chung và bạn sẽ tìm hiểu về chúng trong bài tiếp theo.

  1. Trong hàm khởi tạo chính của lớp RangeRegulator, hãy thêm một tham số initialValue, một thuộc tính minValue riêng tư và một thuộc tính maxValue riêng tư, tất cả đều thuộc loại Int:
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

}
  1. Trong phần nội dung của lớp RangeRegulator, hãy ghi đè các phương thức getValue()setValue():
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}

Các phương thức này đóng vai trò là hàm getter và setter của các thuộc tính.

  1. Trên dòng trước lớp SmartDevice, hãy nhập giao diện ReadWritePropertyKProperty:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

open class SmartDevice(val name: String, val category: String) {
    ...
}

...

class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}

...
  1. Trong lớp RangeRegulator, trên dòng trước phương thức getValue(), hãy xác định một thuộc tính fieldData và khởi tạo thuộc tính đó bằng tham số initialValue:
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}

Thuộc tính này đóng vai trò là trường sao lưu cho biến.

  1. Trong phần nội dung của phương thức getValue(), trả thuộc tính fieldData về:
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}
  1. Trong phần nội dung của phương thức setValue(), hãy kiểm tra xem tham số value đang được chỉ định có nằm trong phạm vi minValue..maxValue hay không trước khi bạn chỉ định cho thuộc tính fieldData:
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value in minValue..maxValue) {
            fieldData = value
        }
    }
}
  1. Trong lớp SmartTvDevice, hãy dùng lớp ủy quyền để xác định các thuộc tính speakerVolumechannelNumber:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)

    private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)

    ...

}
  1. Trong lớp SmartLightDevice, hãy dùng lớp ủy quyền để xác định thuộc tính brightnessLevel:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart Light"

    private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)

    ...

}

10. Thử nghiệm giải pháp

Bạn có thể xem mã giải pháp trong đoạn mã này:

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"
        protected set

    open val deviceType = "unknown"

    open fun turnOn() {
        deviceStatus = "on"
    }

    open fun turnOff() {
        deviceStatus = "off"
    }
}

class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)

    private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    override fun turnOn() {
        super.turnOn()
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }

    override fun turnOff() {
        super.turnOff()
        println("$name turned off")
    }
}

class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart Light"

    private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    override fun turnOn() {
        super.turnOn()
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    override fun turnOff() {
        super.turnOff()
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}

class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    var deviceTurnOnCount = 0
        private set

    fun turnOnTv() {
        deviceTurnOnCount++
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        deviceTurnOnCount--
        smartTvDevice.turnOff()
    }

    fun increaseTvVolume() {
        smartTvDevice.increaseSpeakerVolume()
    }

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }

    fun turnOnLight() {
        deviceTurnOnCount++
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        deviceTurnOnCount--
        smartLightDevice.turnOff()
    }

    fun increaseLightBrightness() {
        smartLightDevice.increaseBrightness()
    }

    fun turnOffAllDevices() {
        turnOffTv()
        turnOffLight()
    }
}

class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value in minValue..maxValue) {
            fieldData = value
        }
    }
}

fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
    smartDevice.turnOn()

    smartDevice = SmartLightDevice("Google Light", "Utility")
    smartDevice.turnOn()
}

Kết quả sẽ như sau:

Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
Google Light turned on. The brightness level is 2.

11. Tham gia thử thách này

  • Trong lớp SmartDevice, hãy xác định một phương thức printDeviceInfo() để in chuỗi "Device name: $name, category: $category, type: $deviceType".
  • Trong lớp SmartTvDevice, hãy xác định một phương thức decreaseVolume() để giảm âm lượng và phương thức previousChannel() chuyển về kênh trước đó.
  • Trong lớp SmartLightDevice, hãy xác định một phương thức decreaseBrightness() để giảm độ sáng.
  • Trong lớp SmartHome, hãy đảm bảo bạn chỉ có thể thực hiện tất cả các hành động khi thuộc tính deviceStatus của mỗi thiết bị được đặt thành một chuỗi "on". Ngoài ra, hãy đảm bảo bạn đã cập nhật chính xác thuộc tính deviceTurnOnCount.

Sau khi bạn hoàn tất việc triển khai:

  • Trong lớp SmartHome, xác định phương thức decreaseTvVolume(), changeTvChannelToPrevious(), printSmartTvInfo(), printSmartLightInfo()decreaseLightBrightness().
  • Gọi các phương thức thích hợp từ lớp SmartTvDeviceSmartLightDevice trong lớp SmartHome.
  • Trong hàm main(), hãy gọi các phương thức được thêm này để kiểm tra chúng.

12. Kết luận

Xin chúc mừng! Bạn đã được học cách xác định lớp và tạo bản sao đối tượng . Bạn cũng đã được học cách tạo mối quan hệ giữa các lớp và uỷ quyền thuộc tính.

Tóm tắt

  • Có 4 nguyên tắc chính của OOP: đóng gói, tóm tắt, kế thừa và đa hình.
  • Các lớp được xác định bằng từ khóa class, đồng thời chứa các thuộc tính và phương thức.
  • Thuộc tính tương tự như biến, ngoại trừ các thuộc tính có thể có phương thức getter và setter tuỳ chỉnh.
  • Hàm khởi tạo sẽ chỉ định cách tạo thực thể của đối tượng cho lớp.
  • Bạn có thể bỏ qua từ khóa constructor khi xác định một hàm khởi tạo chính.
  • Tính kế thừa giúp bạn dễ dàng tái sử dụng mã.
  • Mối quan hệ IS-A đề cập đến tính kế thừa.
  • Mối quan hệ HAS-A đề cập đến tính liên kết.
  • Hệ số xác định mức độ hiển thị đóng vai trò quan trọng trong việc hoàn thành đóng gói.
  • Kotlin cung cấp 4 hệ số xác định mức độ hiển thị: public, private, protectedinternal.
  • Ủy quyền thuộc tính cho phép bạn tái sử dụng mã getter và setter trong nhiều lớp.

Tìm hiểu thêm