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()
và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:
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
, String
và Double
. 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
:
- Trong Kotlin Playground, hãy thay nội dung bằng một hàm
main()
trống:
fun main() {
}
- Trên dòng trước hàm
main()
, hãy xác định lớpSmartDevice
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:
Để 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:
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óaval
để tạo một biến có tênsmartTvDevice
và khởi tạo biến đó dưới dạng một bản sao của lớpSmartDevice
:
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()
và turnOff()
trong lớp SmartDevice
:
- Trong phần nội dung của lớp
SmartDevice
, hãy xác định một phương thứcturnOn()
có phần nội dung trống:
class SmartDevice {
fun turnOn() {
}
}
- Trong phần nội dung của phương thức
turnOn()
, hãy thêm một câu lệnhprintln()
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.")
}
}
- Sau phương thức
turnOn()
, hãy thêm một phương thứcturnOff()
để 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:
Gọi phương thức turnOn()
và turnOff()
trên đối tượng:
- Trong hàm
main()
trên dòng ở sau biếnsmartTvDevice
, hãy gọi phương thứcturnOn()
:
fun main() {
val smartTvDevice = SmartDevice()
smartTvDevice.turnOn()
}
- Trên dòng ở sau phương thức
turnOn()
, hãy gọi phương thứcturnOff()
:
fun main() {
val smartTvDevice = SmartDevice()
smartTvDevice.turnOn()
smartTvDevice.turnOff()
}
- 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
:
- Trên dòng trước phương thức
turnOn()
, hãy xác định thuộc tínhname
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.")
}
}
- Trên dòng sau thuộc tính
name
, hãy xác định thuộc tínhcategory
và chỉ định thuộc tính đó cho chuỗi"Entertainment"
, sau đó xác định thuộc tínhdeviceStatus
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.")
}
}
- Trên dòng sau biến
smartTvDevice
, hãy gọi hàmprintln()
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()
}
- 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()
và set()
(không bắt buộc). Bạn có thể xem cú pháp trong sơ đồ này:
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àmset()
.
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 name
và category
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 name
và category
. Bằng cách triển khai hiện tại, các giá trị của thuộc tính name
và category
đã đượ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ínhname
vàcategory
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:
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:
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:
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 SmartTvDevice
và SmartLightDevice
. 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 SmartTvDevice
và SmartLightDevice
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:
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:
- 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:
- Tạo lớp con
SmartTvDevice
mở rộng lớp cấp caoSmartDevice
:
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ố deviceName
và deviceCategory
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.
- Trong phần nội dung của lớp con
SmartTvDevice
, hãy thêm thuộc tínhspeakerVolume
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
}
}
}
- 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 vi0..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
}
}
}
- 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.")
}
}
- 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.")
}
}
- Trên dòng sau lớp con
SmartTvDevice
, hãy xác định lớp conSmartLightDevice
mở rộng lớp cấp caoSmartDevice
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
}
- Trong phần nội dung lớp con
SmartLightDevice
, hãy xác định một thuộc tínhbrightnessLevel
được gán cho giá trị0
bằng một hàm setter chỉ định một phạm vi0..100
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
}
- 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:
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 là 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 là 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
:
- Ở giữa lớp
SmartLightDevice
và hàmmain()
, hãy xác định một lớpSmartHome
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
...
}
class SmartHome {
}
fun main() {
...
}
- Trong hàm khởi tạo lớp
SmartHome
, hãy dùng từ khóaval
để tạo thuộc tínhsmartTvDevice
thuộc loạiSmartTvDevice
:
// The SmartHome class HAS-A smart TV device.
class SmartHome(val smartTvDevice: SmartTvDevice) {
}
- Trong phần nội dung của lớp
SmartHome
, hãy xác định một phương thứcturnOnTv()
để gọi phương thứcturnOn()
trên thuộc tínhsmartTvDevice
:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
}
- Trên dòng sau phương thức
turnOnTv()
, hãy xác định phương thứcturnOffTv()
để gọi phương thứcturnOff()
trên thuộc tínhsmartTvDevice
:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
}
- Trên dòng sau phương thức
turnOffTv()
, hãy xác định phương thứcincreaseTvVolume()
để gọi phương thứcincreaseSpeakerVolume()
trên thuộc tínhsmartTvDevice
, sau đó xác định phương thứcchangeTvChannelToNext()
để gọi phương thứcnextChannel()
trên thuộc tínhsmartTvDevice
:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
fun increaseTvVolume() {
smartTvDevice.increaseSpeakerVolume()
}
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
}
- Trong hàm khởi tạo lớp
SmartHome
, di chuyển tham số thuộc tínhsmartTvDevice
sang dòng riêng, theo sau là dấu phẩy:
class SmartHome(
val smartTvDevice: SmartTvDevice,
) {
...
}
- Trên dòng sau thuộc tính
smartTvDevice
, dùng từ khóaval
để xác định thuộc tínhsmartLightDevice
thuộc loạiSmartLightDevice
:
// The SmartHome class HAS-A smart TV device and smart light.
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
}
- Trong
SmartHome
nội dung, xác định phương thứcturnOnLight()
để gọi phương thứcturnOn()
trên đối tượngsmartLightDevice
và một đối tượngturnOffLight()
để gọi phương thứcturnOff()
trên đối tượngsmartLightDevice
:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
smartLightDevice.turnOn()
}
fun turnOffLight() {
smartLightDevice.turnOff()
}
}
- Trên dòng sau phương thức
turnOffLight()
, hãy xác định phương thứcincreaseLightBrightness()
để gọi phương thứcincreaseBrightness()
trên thuộc tínhsmartLightDevice
:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
smartLightDevice.turnOn()
}
fun turnOffLight() {
smartLightDevice.turnOff()
}
fun increaseLightBrightness() {
smartLightDevice.increaseBrightness()
}
}
- Trên dòng sau phương thức
increaseLightBrightness()
, hãy xác định phương thứcturnOffAllDevices()
để gọi phương thứcturnOffTv()
và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()
và 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()
và turnOff()
:
- 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
}
}
- Trong phần nội dung của lớp
SmartLightDevice
, hãy xác định một phương thứcturnOn()
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() {
}
}
- Trong phần nội dung của phương thức
turnOn()
, thiết lập thuộc tínhdeviceStatus
thành chuỗi "on
", thiết lập thuộc tínhbrightnessLevel
thành một giá trị2
, rồi thêm câu lệnhprintln()
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.")
}
- Trong phần nội dung của lớp
SmartLightDevice
, hãy xác định một phương thứcturnOff()
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() {
}
}
- Trong phần nội dung của phương thức
turnOff()
, thiết lập thuộc tínhdeviceStatus
thành chuỗi "off
", thiết lập thuộc tínhbrightnessLevel
thành một giá trị0
rồi thêm câu lệnhprintln()
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")
}
}
- Trong lớp con
SmartLightDevice
trước từ khoáfun
của phương thứcturnOn()
và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.
- Trong phần nội dung của lớp
SmartTvDevice
, hãy xác định một phương thứcturnOn()
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() {
}
}
- Trong phần nội dung của phương thức
turnOn()
, hãy thiết lập thuộc tínhdeviceStatus
thành chuỗi "on
" rồi thêm một câu lệnhprintln()
, 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."
)
}
}
- Trong phần nội dung của lớp
SmartTvDevice
sau phương thứcturnOn()
, hãy xác định phương thứcturnOff()
có phần nội dung trống:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
...
fun turnOn() {
...
}
fun turnOff() {
}
}
- Trong phần nội dung của phương thức
turnOff()
, hãy thiết lập thuộc tínhdeviceStatus
thành chuỗi "off
" rồi thêm câu lệnhprintln()
, 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")
}
}
- Trong lớp
SmartTvDevice
trước từ khoáfun
của phương thứcturnOn()
và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")
}
}
- Trong hàm
main()
, hãy dùng từ khoávar
để xác định biếnsmartDevice
thuộc loạiSmartDevice
sẽ tạo thực thể cho đối tượngSmartTvDevice
để lấy đối số"Android
TV"
và đối số"Entertainment"
:
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
}
- Trên dòng sau biến
smartDevice
, gọi phương thứcturnOn()
trên đối tượngsmartDevice
:
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
smartDevice.turnOn()
}
- 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.
- Trên dòng sau khi gọi phương thức
turnOn()
, chỉ định lại biếnsmartDevice
để tạo thực thể cho một lớpSmartLightDevice
nhận đối số"Google
Light"
và một đối số"Utility"
, và sau đó gọi phương thứcturnOn()
trên tham chiếu đối tượngsmartDevice
:
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
smartDevice.turnOn()
smartDevice = SmartLightDevice("Google Light", "Utility")
smartDevice.turnOn()
}
- 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()
và 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 SmartTvDevice
và SmartLightDevice
: 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:
Sử dụng lại mã trong lớp cấp cao SmartDevice
:
- Xoá các câu lệnh
println()
khỏi phương thứcturnOn()
vàturnOff()
, đồng thời di chuyển mã trùng lặp từ lớp conSmartTvDevice
vàSmartLightDevice
sang lớp cấp caoSmartDevice
:
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open fun turnOn() {
deviceStatus = "on"
}
open fun turnOff() {
deviceStatus = "off"
}
}
- Dùng từ khoá
super
để gọi các phương thức từ lớpSmartDevice
trong các lớp conSmartTvDevice
vàSmartLightDevice
:
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
:
- Trong lớp cấp cao
SmartDevice
trên dòng sau thuộc tínhdeviceStatus
, hãy dùng từ khoáopen
vàval
để xác định thuộc tínhdeviceType
được đặt thành chuỗi"unknown"
:
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open val deviceType = "unknown"
...
}
- Trong lớp
SmartTvDevice
, hãy dùng từ khoáoverride
vàval
để xác định thuộc tínhdeviceType
được đặt thành chuỗi"Smart
TV"
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart TV"
...
}
- Trong lớp
SmartLightDevice
, hãy dùng từ khoáoverride
vàval
để xác định thuộc tínhdeviceType
đượ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 định
public
.. 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:
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:
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:
Đố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
:
- Trong thuộc tính
deviceStatus
của lớp cấp caoSmartDevice
, thêm hệ số sửa đổiprotected
vào hàmset()
:
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
...
}
- Trong lớp
SmartHome
, hãy xác định một thuộc tínhdeviceTurnOnCount
đượ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
...
}
- Thêm thuộc tính
deviceTurnOnCount
theo sau là toán tử số học++
vào phương thứcturnOnTv()
vàturnOnLight()
. Sau đó, thêm thuộc tínhdeviceTurnOnCount
theo sau là toán tử số học--
vào phương thứcturnOffTv()
và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()
}
...
}
Đối tượng sửa đổi chế độ hiển thị 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:
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:
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:
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 |
| ✔ | 𝗫 | 𝗫 | 𝗫 |
| ✔ | ✔ | 𝗫 | 𝗫 |
| ✔ | ✔ | ✔ | 𝗫 |
| ✔ | ✔ | ✔ | ✔ |
Trong lớp con SmartTvDevice
, bạn không nên cho phép kiểm soát các thuộc tính speakerVolume
và channelNumber
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()
và 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 SmartTvDevice
và SmartLightDevice
:
- 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ínhspeakerVolume
vàchannelNumber
:
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
}
}
...
}
- Trong lớp
SmartLightDevice
, hãy thêm một đối tượng sửa đổiprivate
vào thuộc tínhbrightnessLevel
:
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
, channelNumber
và brightnessLevel
trong lớp SmartTvDevice
và SmartLightDevice
. 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:
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:
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
:
- Trước hàm
main()
, tạo một lớpRangeRegulator
sẽ triển khai giao diệnReadWriteProperty<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.
- 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ínhminValue
riêng tư và một thuộc tínhmaxValue
riêng tư, tất cả đều thuộc loạiInt
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
}
- Trong phần nội dung của lớp
RangeRegulator
, hãy ghi đè các phương thứcgetValue()
và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.
- Trên dòng trước lớp
SmartDevice
, hãy nhập giao diệnReadWriteProperty
vàKProperty
:
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) {
}
}
...
- Trong lớp
RangeRegulator
, trên dòng trước phương thứcgetValue()
, hãy xác định một thuộc tínhfieldData
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.
- Trong phần nội dung của phương thức
getValue()
, trả thuộc tínhfieldData
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) {
}
}
- 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 viminValue..maxValue
hay không trước khi bạn chỉ định cho thuộc tínhfieldData
:
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
}
}
}
- Trong lớp
SmartTvDevice
, hãy dùng lớp ủy quyền để xác định các thuộc tínhspeakerVolume
vàchannelNumber
:
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)
...
}
- Trong lớp
SmartLightDevice
, hãy dùng lớp ủy quyền để xác định thuộc tínhbrightnessLevel
:
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ứcprintDeviceInfo()
để in chuỗi"Device
name:
$name,
category:
$category,
type:
$deviceType"
. - Trong lớp
SmartTvDevice
, hãy xác định một phương thứcdecreaseVolume()
để giảm âm lượng và phương thứcpreviousChannel()
chuyển về kênh trước đó. - Trong lớp
SmartLightDevice
, hãy xác định một phương thứcdecreaseBrightness()
để 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ínhdeviceStatus
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ínhdeviceTurnOnCount
.
Sau khi bạn hoàn tất việc triển khai:
- Trong lớp
SmartHome
, xác định phương thứcdecreaseTvVolume()
,changeTvChannelToPrevious()
,printSmartTvInfo()
,printSmartLightInfo()
vàdecreaseLightBrightness()
. - Gọi các phương thức thích hợp từ lớp
SmartTvDevice
vàSmartLightDevice
trong lớpSmartHome
. - 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.
- Đối tượng sửa đổi chế độ 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
,protected
vàinternal
. - Ủ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.