1. 开始之前
本 Codelab 将教您如何在 Kotlin 中使用类和对象。
类提供了一个蓝图,可以根据它构建对象。对象是类的一个实例,包含特定于该对象的数据。您可以互换使用对象或类实例。
打个比方,想象您要建造一座房子。类类似于建筑师的设计图,也称为蓝图。蓝图不是房子;它是建造房子的说明。房子是根据蓝图建造的实际事物,即对象。
就像房屋蓝图指定了多个房间,每个房间都有自己的设计和用途一样,每个类也有自己的设计和用途。要了解如何设计类,您需要熟悉
OOP 可帮助您将复杂的现实问题简化为更小的对象。OOP 有四个基本概念,您将在本 Codelab 的稍后部分详细了解每个概念。
- 封装。 将相关属性和对其执行操作的方法封装在一个类中。例如,考虑您的手机。它封装了摄像头、显示屏、存储卡以及其他一些硬件和软件组件。您无需担心组件内部是如何连接的。
- 抽象。 封装的延伸。其思想是尽可能隐藏内部实现逻辑。例如,要使用手机拍照,您只需打开相机应用,将手机对准想要拍摄的场景,然后点击按钮即可拍照。您无需知道相机应用是如何构建的,也无需知道手机上的相机硬件实际上是如何工作的。简而言之,相机应用的内部机制以及手机相机如何捕捉照片已被抽象化,以便您可以执行重要任务。
- 继承。 通过建立父子关系,使您可以在其他类的特征和行为的基础上构建一个类。例如,有许多不同的制造商生产运行 Android 操作系统的各种移动设备,但每种设备的 UI 都不同。换句话说,制造商继承了 Android 操作系统功能,并在其之上构建了自定义内容。
- 多态。 该词改编自希腊词根
poly- ,意为“许多”,以及-morphism ,意为“形式”。多态性是能够以单一、通用方式使用不同对象的能力。例如,当您将蓝牙音箱连接到手机时,手机只需要知道有一个设备可以通过蓝牙播放音频。但是,您可以选择各种蓝牙音箱,您的手机无需知道如何与每个特定的音箱一起工作。
最后,您将了解属性委托,它们提供可重用的代码,以简洁的语法管理属性值。在本 Codelab 中,您将在构建智能家居应用的类结构时学习这些概念。
前提条件
- 如何在Kotlin Playground中打开、编辑和运行代码。
- 了解 Kotlin 编程基础,包括变量、函数以及
println()
和main()
函数。
您将学习的内容
- OOP 概述。
- 什么是类。
- 如何使用构造函数、函数和属性定义类。
- 如何实例化对象。
- 什么是继承。
- IS-A 和 HAS-A 关系之间的区别。
- 如何重写属性和函数。
- 什么是可见性修饰符。
- 什么是委托以及如何使用
by
委托。
您将构建的内容
- 一个智能家居类结构。
- 表示智能设备的类,例如智能电视和智能灯。
您需要的内容
- 一台具有互联网访问和网络浏览器的计算机。
2. 定义类
定义类时,您指定该类的所有对象应该拥有的属性和方法。
类定义以 class
关键字开头,后跟一个名称和一对大括号。开大括号之前的语法部分也称为类头。在大括号中,您可以为类指定属性和函数。您很快就会学习属性和函数。您可以在此图表中看到类定义的语法。
以下是建议的类命名约定:
- 您可以选择任何您想要的类名,但不要使用 Kotlin 关键字作为类名,例如
fun
关键字。 - 类名采用 PascalCase 格式编写,因此每个单词的首字母都大写,且单词之间没有空格。例如,在 SmartDevice 中,每个单词的首字母大写,且单词之间没有空格。
一个类包含三个主要部分:
- 属性。指定类对象属性的变量。
- 方法。包含类行为和操作的函数。
- 构造函数。一个特殊的成员函数,用于在定义它的程序中创建类的实例。
这不是您第一次使用类。在之前的 Codelab 中,您学习了数据类型,例如 Int
、Float
、String
和 Double
数据类型。这些数据类型在 Kotlin 中定义为类。当您定义变量(如此代码段所示)时,您会创建一个 Int
类的对象,并使用 1
值实例化该对象。
val number: Int = 1
定义一个 SmartDevice
类
- 在Kotlin Playground中,将内容替换为空的
main()
函数。
fun main() {
}
- 在
main()
函数之前的行上,定义一个SmartDevice
类,其主体包含一个//
empty
body
注释。
class SmartDevice {
// empty body
}
fun main() {
}
3. 创建类实例
正如您所学,类是对象的蓝图。Kotlin 运行时使用类(或蓝图)来创建该特定类型的对象。使用 SmartDevice
类,您拥有了智能设备的蓝图。要在程序中拥有一个SmartDevice
对象实例。实例化语法以类名开头,后跟一对括号,如此图所示。
要使用对象,您需要创建对象并将其赋值给变量,这类似于您定义变量的方式。您使用 val
关键字创建不可变变量,使用 var
关键字创建可变变量。val
或 var
关键字后跟变量名,然后是 =
赋值运算符,然后是类对象的实例化。您可以在此图表中看到语法。
将 SmartDevice
类实例化为对象
- 在
main()
函数中,使用val
关键字创建一个名为smartTvDevice
的变量,并将其初始化为SmartDevice
类的一个实例。
fun main() {
val smartTvDevice = SmartDevice()
}
4. 定义类方法
在单元 1 中,您了解到:
- 函数的定义使用
fun
关键字,后跟一对括号和一对大括号。大括号包含代码,即执行任务所需的指令。 - 调用函数会执行该函数中包含的代码。
类可以执行的操作在类中定义为函数。例如,假设您拥有一个智能设备,一个智能电视或智能灯,您可以用手机将其打开和关闭。智能设备在编程中转换为 SmartDevice
类,打开和关闭它的操作由 turnOn()
和 turnOff()
函数表示,它们启用开和关的行为。
在类中定义函数的语法与您之前学到的语法相同。唯一的区别是函数放置在类主体中。当您在类主体中定义
在 SmartDevice
类中定义 turnOn()
和 turnOff()
方法
- 在
SmartDevice
类的主体中,定义一个空的turnOn()
方法。
class SmartDevice {
fun turnOn() {
}
}
- 在
turnOn()
方法的主体中,添加一个println()
语句,然后向其传递一个"Smart
device
is
turned
on."
字符串。
class SmartDevice {
fun turnOn() {
println("Smart device is turned on.")
}
}
- 在
turnOn()
方法之后,添加一个turnOff()
方法,该方法打印一个"Smart
device
is
turned
off."
字符串。
class SmartDevice {
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
在对象上调用方法
到目前为止,您已经定义了一个作为智能设备蓝图的类,创建了该类的一个实例,并将该实例分配给了变量。现在您将使用 SmartDevice
类的方法来打开和关闭设备。
调用类中的方法与您在之前的 Codelab 中从 main()
函数调用其他函数的方式类似。例如,如果您需要从 turnOn()
方法调用 turnOff()
方法,您可以编写类似于此代码片段的代码:
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()
...
}
...
}
要在类外部调用类方法,首先是类对象,后跟 .
运算符、函数名和一对括号。如果适用,括号中包含方法所需的参数。您可以在此图表中看到语法。
在对象上调用 turnOn()
和 turnOff()
方法
- 在
main()
函数中,在smartTvDevice
变量之后的行上,调用turnOn()
方法。
fun main() {
val smartTvDevice = SmartDevice()
smartTvDevice.turnOn()
}
- 在
turnOn()
方法之后的行上,调用turnOff()
方法。
fun main() {
val smartTvDevice = SmartDevice()
smartTvDevice.turnOn()
smartTvDevice.turnOff()
}
- 运行代码。
输出如下:
Smart device is turned on. Smart device is turned off.
5. 定义类属性
在单元 1 中,您学习了变量,变量是单个数据片段的容器。您学习了如何使用 val
关键字创建只读变量,以及如何使用 var
关键字创建可变变量。
虽然方法定义了类可以执行的操作,但属性定义了类的特征或数据属性。例如,智能设备具有以下属性:
- 名称。设备名称。
- 类别。智能设备类型,例如娱乐、实用或烹饪。
- 设备状态。设备是打开、关闭、在线还是离线。设备连接到互联网时被视为在线。否则被视为离线。
属性本质上是在类主体而不是函数主体中定义的变量。这意味着定义属性和变量的语法是相同的。您使用 val
关键字定义不可变属性,使用 var
关键字定义可变属性。
将上述特性实现为 SmartDevice
类的属性
- 在
turnOn()
方法之前的行上,定义name
属性并将其分配给字符串"Android
TV"
。
class SmartDevice {
val name = "Android TV"
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
- 在
name
属性之后的行上,定义category
属性并将其分配给字符串"Entertainment"
,然后定义一个deviceStatus
属性并将其分配给字符串"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.")
}
}
- 在
smartTvDevice
变量之后的行上,调用println()
函数,然后向其传递字符串"Device
name
is:
${smartTvDevice.name}"
。
fun main() {
val smartTvDevice = SmartDevice()
println("Device name is: ${smartTvDevice.name}")
smartTvDevice.turnOn()
smartTvDevice.turnOff()
}
- 运行代码。
输出如下:
Device name is: Android TV Smart device is turned on. Smart device is turned off.
属性中的 getter 和 setter 函数
属性能做的比变量更多。例如,想象您创建了一个类结构来表示智能电视。您执行的常见操作之一是增大和减小音量。为了在编程中表示此操作,您可以创建一个名为 speakerVolume
的属性,它保存电视扬声器上设置的当前音量级别,但音量的值有一个范围。您可以设置的最小音量为 0,而最大音量为 100。为了确保 speakerVolume
属性永远不超过 100 或低于 0,您可以编写一个name
属性转换为大写。
在深入了解如何实现这些属性之前,您需要了解声明它们的完整语法。定义get()
和 set()
函数。您可以在此图表中看到语法。
当您没有为属性定义 getter 和 setter 函数时,Kotlin 编译器会在内部创建这些函数。例如,如果您使用 var
关键字定义 speakerVolume
属性并为其分配 2
值,编译器会自动生成 getter 和 setter 函数,如此代码片段所示。
var speakerVolume = 2
get() = field
set(value) {
field = value
}
您不会在代码中看到这些行,因为它们是编译器在后台添加的。
- 它以
val
关键字开头。 val
类型的变量是只读变量,因此它们没有set()
函数。
Kotlin 属性使用get()
或 set()
属性函数访问它。
要在 get()
函数中读取属性值或在 set()
函数中更新值,您需要使用属性的field
标识符引用。
例如,当您想要在 set()
函数中更新属性值时,您可以使用 set()
函数的参数,该参数被称为 value
参数,并将其分配给 field
变量,如此代码片段所示。
var speakerVolume = 2
set(value) {
field = value
}
例如,要确保分配给 speakerVolume
属性的值在 0 到 100 的范围内,您可以实现
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
set()
函数使用 in
关键字后跟值范围来检查 Int
值是否在 0 到 100 的范围内。如果值在预期范围内,则更新 field
值。如果不在,属性值将保持不变。
您将在本 Codelab 的
6. 定义构造函数
构造函数的主要目的是指定如何创建类的对象。换句话说,构造函数初始化对象并使对象可供使用。您在实例化对象时完成了此操作。构造函数中的代码在实例化类对象时执行。您可以定义带参数或不带参数的构造函数。
默认构造函数
默认构造函数是没有参数的构造函数。您可以定义一个默认构造函数,如此代码片段所示:
class SmartDevice constructor() {
...
}
Kotlin 旨在简洁,因此如果构造函数上没有注解或可见性修饰符(您很快就会学习到),您可以移除 constructor
关键字。如果构造函数没有参数,您也可以移除括号,如此代码片段所示:
class SmartDevice {
...
}
Kotlin 编译器会自动生成默认构造函数。您不会在代码中看到自动生成的默认构造函数,因为它是由编译器在后台添加的。
定义带参数的构造函数
在 SmartDevice
类中,name
和 category
属性是不可变的。您需要确保 SmartDevice
类的所有实例都初始化 name
和 category
属性。在当前的实现中,name
和 category
属性的值是硬编码的。这意味着所有智能设备都使用字符串 "Android
TV"
命名,并使用字符串 "Entertainment"
分类。
为了保持不可变性但避免硬编码值,使用带参数的构造函数来初始化它们。
- 在
SmartDevice
类中,将name
和category
属性移到构造函数中,不分配默认值。
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.")
}
}
现在构造函数接受参数来设置其属性,因此实例化此类对象的方式也会改变。您可以在此图表中看到实例化对象的完整语法。
这是代码表示:
SmartDevice("Android TV", "Entertainment")
传递给构造函数的两个参数都是字符串。有点不清楚哪个值应该赋给哪个参数。要解决此问题,类似于您传递函数参数的方式,您可以创建一个带命名参数的构造函数,如此代码片段所示:
SmartDevice(name = "Android TV", category = "Entertainment")
Kotlin 中有两种主要类型的构造函数:
- 主构造函数。一个类只能有一个主构造函数,它被定义为类头的一部分。主构造函数可以是默认构造函数或带参数的构造函数。主构造函数没有主体。这意味着它不能包含任何代码。
- 次构造函数。一个类可以有多个次构造函数。您可以定义带参数或不带参数的次构造函数。次构造函数可以初始化类并具有主体,其中可以包含初始化逻辑。如果类具有主构造函数,则每个次构造函数都需要初始化主构造函数。
您可以使用主构造函数在类头中初始化属性。传递给构造函数的参数会分配给属性。定义主构造函数的语法以类名开头,后跟 constructor
关键字和一对括号。括号中包含主构造函数的参数。如果参数多于一个,则用逗号分隔参数定义。您可以在此图表中看到定义主构造函数的完整语法。
次构造函数包含在类主体中,其语法包括三个部分:
- 次构造函数声明。次构造函数定义以
constructor
关键字开头,后跟括号。如果适用,括号包含次构造函数所需的参数。 - 主构造函数初始化。初始化以冒号开头,后跟
this
关键字和一对括号。如果适用,括号包含主构造函数所需的参数。 - 次构造函数主体。主构造函数的初始化后跟一对大括号,其中包含次构造函数的主体。
您可以在此图表中看到语法。
例如,想象您想要集成一个由智能设备提供商开发的 API。但是,该 API 返回 Int
类型的状态码以指示初始设备状态。如果设备0
值;如果设备1
值。对于任何其他整数值,状态被视为SmartDevice
类中创建一个次构造函数,以将此 statusCode
参数转换为字符串表示形式,如此代码片段所示:
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. 实现类之间的关系
继承使您可以在其他类的特征和行为的基础上构建一个类。它是一种强大的机制,可帮助您编写可重用代码并建立类之间的关系。
例如,市场上有许多智能设备,例如智能电视、智能灯和智能开关。在编程中表示智能设备时,它们共享一些共同的属性,例如名称、类别和状态。它们也具有共同的行为,例如打开和关闭的能力。
然而,每个智能设备的打开或关闭方式不同。例如,要打开电视,您可能需要打开显示屏,然后设置上次已知的音量级别和频道。另一方面,要打开灯,您可能只需要增大或减小亮度。
此外,每个智能设备都有更多的功能和操作可以执行。例如,使用电视,您可以调整音量和更改频道。使用灯,您可以调整亮度或颜色。
简而言之,所有智能设备都具有不同的功能,但共享一些共同的特性。您可以将这些共同特性复制到每个智能设备类中,也可以通过继承使代码可重用。
为此,您需要创建一个 SmartDevice
父类,并定义这些共同的属性和行为。然后,您可以创建子类,例如 SmartTvDevice
和 SmartLightDevice
类,它们继承父类的属性。
在编程术语中,我们说 SmartTvDevice
和 SmartLightDevice
类SmartDevice
父类。父类也称为
然而,在 Kotlin 中,默认情况下所有类都是 final 的,这意味着您无法扩展它们,因此您必须定义它们之间的关系。
定义 SmartDevice
超类及其子类之间的关系
- 在
SmartDevice
超类中,在class
关键字之前添加一个open
关键字,使其可扩展。
open class SmartDevice(val name: String, val category: String) {
...
}
open
关键字告知编译器该类是可扩展的,因此现在其他类可以扩展它。
创建子类的语法以创建类头开始,就像您迄今为止所做的那样。构造函数的结束括号后跟一个空格、一个冒号、另一个空格、超类名和一对括号。如果必要,括号包含超类构造函数所需的参数。您可以在此图表中看到语法。
- 创建一个扩展
SmartDevice
超类的SmartTvDevice
子类
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
}
SmartTvDevice
的 constructor
定义没有指定属性是可变的还是不可变的。这意味着 deviceName
和 deviceCategory
参数只是 constructor
参数,而不是类属性。您将无法在类中使用它们,而只是将它们传递给超类构造函数。
- 在
SmartTvDevice
子类的主体中,添加您在学习 getter 和 setter 函数时创建的speakerVolume
属性。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
}
- 定义一个
channelNumber
属性,并将其分配给一个1
值,并带有一个指定范围为0..200
的 setter 函数。
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
}
}
}
- 定义一个
increaseSpeakerVolume()
方法,该方法增加音量并打印字符串"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.")
}
}
- 添加一个
nextChannel()
方法,该方法增加频道号并打印字符串"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.")
}
}
- 在
SmartTvDevice
子类之后的行上,定义一个扩展SmartDevice
超类的SmartLightDevice
子类。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
}
- 在
SmartLightDevice
子类的主体中,定义一个brightnessLevel
属性,并将其分配给一个0
值,并带有一个指定范围为0..100
的 setter 函数。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
}
- 定义一个
increaseBrightness()
方法,该方法增加灯的亮度并打印字符串"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.")
}
}
类之间的关系
当您使用继承时,您在两个类之间建立了一种称为
IS-A 关系
当您指定 SmartDevice
超类和 SmartTvDevice
子类之间的 IS-A 关系时,这意味着 SmartDevice
超类可以做的任何事情,SmartTvDevice
子类也可以做。这种关系是单向的,所以你可以说每台智能电视
// Smart TV IS-A smart device.
class SmartTvDevice : SmartDevice() {
}
不要仅仅为了实现代码复用而使用继承。在做决定之前,检查两个类之间是否存在关系。如果它们之间存在一些关系,检查它们是否真正符合 IS-A 关系。问问自己,“我可以把子类说成是超类吗?”。例如,Android
HAS-A 关系
HAS-A 关系是指定两个类之间关系的另一种方式。例如,您很可能会在家里使用智能电视。在这种情况下,智能电视和家之间存在关系。家包含一个智能设备,或者换句话说,家
到目前为止,您已经创建了几个智能设备。现在,您将创建包含智能设备的 SmartHome
类。SmartHome
类允许您与智能设备交互。
使用 HAS-A 关系定义 SmartHome
类
- 在
SmartLightDevice
类和main()
函数之间,定义一个SmartHome
类。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
...
}
class SmartHome {
}
fun main() {
...
}
- 在
SmartHome
类构造函数中,使用val
关键字创建一个SmartTvDevice
类型的smartTvDevice
属性。
// The SmartHome class HAS-A smart TV device.
class SmartHome(val smartTvDevice: SmartTvDevice) {
}
- 在
SmartHome
类的主体中,定义一个turnOnTv()
方法,该方法调用smartTvDevice
属性上的turnOn()
方法。
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
}
- 在
turnOnTv()
方法之后的行上,定义一个turnOffTv()
方法,该方法调用smartTvDevice
属性上的turnOff()
方法。
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
}
- 在
turnOffTv()
方法之后的行上,定义一个increaseTvVolume()
方法,该方法调用smartTvDevice
属性上的increaseSpeakerVolume()
方法,然后定义一个changeTvChannelToNext()
方法,该方法调用smartTvDevice
属性上的nextChannel()
方法。
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
fun increaseTvVolume() {
smartTvDevice.increaseSpeakerVolume()
}
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
}
- 在
SmartHome
类构造函数中,将smartTvDevice
属性参数移到单独的行,后跟逗号。
class SmartHome(
val smartTvDevice: SmartTvDevice,
) {
...
}
- 在
smartTvDevice
属性之后的行上,使用val
关键字定义一个SmartLightDevice
类型的smartLightDevice
属性。
// The SmartHome class HAS-A smart TV device and smart light.
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
}
- 在
SmartHome
主体中,定义一个turnOnLight()
方法,该方法调用smartLightDevice
对象上的turnOn()
方法,并定义一个turnOffLight()
方法,该方法调用smartLightDevice
对象上的turnOff()
方法。
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
smartLightDevice.turnOn()
}
fun turnOffLight() {
smartLightDevice.turnOff()
}
}
- 在
turnOffLight()
方法之后的行上,定义一个increaseLightBrightness()
方法,该方法调用smartLightDevice
属性上的increaseBrightness()
方法。
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
smartLightDevice.turnOn()
}
fun turnOffLight() {
smartLightDevice.turnOff()
}
fun increaseLightBrightness() {
smartLightDevice.increaseBrightness()
}
}
- 在
increaseLightBrightness()
方法之后的行上,定义一个turnOffAllDevices()
方法,该方法调用turnOffTv()
和turnOffLight()
方法。
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun turnOffAllDevices() {
turnOffTv()
turnOffLight()
}
}
从子类重写超类方法
如前所述,尽管所有智能设备都支持打开和关闭功能,但它们执行功能的方式不同。为了提供这种特定于设备的行为,您需要重写超类中定义的 turnOn()
和 turnOff()
方法。重写意味着拦截操作,通常是为了手动控制。当您重写一个方法时,子类中的方法会中断超类中定义的方法的执行,并提供自己的执行。
重写 SmartDevice
类的 turnOn()
和 turnOff()
方法
- 在
SmartDevice
超类的主体中,在每个方法的fun
关键字之前,添加一个open
关键字。
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open fun turnOn() {
// function body
}
open fun turnOff() {
// function body
}
}
- 在
SmartLightDevice
类的主体中,定义一个空的turnOn()
方法。
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() {
}
}
- 在
turnOn()
方法的主体中,将deviceStatus
属性设置为字符串 "on
",将brightnessLevel
属性设置为值2
,并添加一个println()
语句,然后向其传递字符串"$name
turned
on.
The
brightness
level
is
$brightnessLevel."
。
fun turnOn() {
deviceStatus = "on"
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
- 在
SmartLightDevice
类的主体中,定义一个空的turnOff()
方法。
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() {
}
}
- 在
turnOff()
方法的主体中,将deviceStatus
属性设置为字符串 "off
",将brightnessLevel
属性设置为值0
,并添加一个println()
语句,然后向其传递字符串"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")
}
}
- 在
SmartLightDevice
子类中,在turnOn()
和turnOff()
方法的fun
关键字之前,添加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")
}
}
override
关键字通知 Kotlin 运行时执行子类中定义的方法中包含的代码。
- 在
SmartTvDevice
类的主体中,定义一个空的turnOn()
方法。
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() {
}
}
- 在
turnOn()
方法的主体中,将deviceStatus
属性设置为字符串 "on
",并添加一个println()
语句,然后向其传递字符串"$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."
)
}
}
- 在
SmartTvDevice
类中,在turnOn()
方法之后,定义一个空的turnOff()
方法。
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
...
fun turnOn() {
...
}
fun turnOff() {
}
}
- 在
turnOff()
方法的主体中,将deviceStatus
属性设置为字符串 "off
",并添加一个println()
语句,然后向其传递字符串"$name
turned
off"
。
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
...
fun turnOn() {
...
}
fun turnOff() {
deviceStatus = "off"
println("$name turned off")
}
}
- 在
SmartTvDevice
类中,在turnOn()
和turnOff()
方法的fun
关键字之前,添加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")
}
}
- 在
main()
函数中,使用var
关键字定义一个SmartDevice
类型的smartDevice
变量,该变量实例化一个SmartTvDevice
对象,该对象接受"Android
TV"
参数和"Entertainment"
参数。
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
}
- 在
smartDevice
变量之后的行上,调用smartDevice
对象上的turnOn()
方法。
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
smartDevice.turnOn()
}
- 运行代码。
输出如下:
Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
- 在调用
turnOn()
方法之后的行上,将smartDevice
变量重新分配以实例化一个SmartLightDevice
类,该类接受"Google
Light"
参数和"Utility"
参数,然后调用smartDevice
对象引用上的turnOn()
方法。
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
smartDevice.turnOn()
smartDevice = SmartLightDevice("Google Light", "Utility")
smartDevice.turnOn()
}
- 运行代码。
输出如下:
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.
这是多态性的一个例子。代码在 SmartDevice
类型的变量上调用 turnOn()
方法,并且根据变量的实际值,可以执行 turnOn()
方法的不同实现。
使用 super
关键字在子类中重用超类代码
当您仔细查看 turnOn()
和 turnOff()
方法时,您会注意到在 SmartTvDevice
和 SmartLightDevice
子类中调用这些方法时,deviceStatus
变量的更新方式相似:代码重复了。您可以在 SmartDevice
类中更新状态时重用代码。
要从子类调用超类中被重写的方法,您需要使用 super
关键字。从超类调用方法类似于从类外部调用方法。您需要使用 super
关键字,而不是在对象和方法之间使用 .
运算符,这会通知 Kotlin 编译器调用超类而不是子类中的方法。
从超类调用方法的语法以 super
关键字开头,后跟 .
运算符、函数名和一对括号。如果适用,括号中包含参数。您可以在此图表中看到语法。
重用 SmartDevice
超类中的代码
- 从
turnOn()
和turnOff()
方法中移除println()
语句,并将重复的代码从SmartTvDevice
和SmartLightDevice
子类移动到SmartDevice
超类。
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open fun turnOn() {
deviceStatus = "on"
}
open fun turnOff() {
deviceStatus = "off"
}
}
- 在
SmartTvDevice
和SmartLightDevice
子类中使用super
关键字调用SmartDevice
类中的方法。
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")
}
}
从子类重写超类属性
与方法类似,您也可以按照相同的步骤重写属性。
重写 deviceType
属性
- 在
SmartDevice
超类中,在deviceStatus
属性之后的行上,使用open
和val
关键字定义一个deviceType
属性,并将其设置为字符串"unknown"
。
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open val deviceType = "unknown"
...
}
- 在
SmartTvDevice
类中,使用override
和val
关键字定义一个deviceType
属性,并将其设置为字符串"Smart
TV"
。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart TV"
...
}
- 在
SmartLightDevice
类中,使用override
和val
关键字定义一个deviceType
属性,并将其设置为字符串"Smart
Light"
。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart Light"
...
}
8. 可见性修饰符
可见性修饰符在实现封装方面发挥着重要作用。
- 在
类 中,它们允许您将属性和方法隐藏起来,以免受到类外部的未经授权的访问。 - 在
包 中,它们允许您将类和接口隐藏起来,以免受到包外部的未经授权的访问。
Kotlin 提供四种可见性修饰符:
public
。默认可见性修饰符。使声明可在任何地方访问。您希望在类外部使用的属性和方法被标记为 public。private
。使声明可在同一类或源文件中访问。
可能有一些属性和方法仅在类内部使用,并且您不一定希望其他类使用它们。可以使用 private
可见性修饰符标记这些属性和方法,以确保其他类不会意外地访问它们。
protected
。使声明可在子类中访问。您希望在其定义类及其子类中使用的属性和方法被标记为protected
可见性修饰符。internal
。使声明可在同一模块中访问。内部修饰符类似于 private,但只要在同一模块中访问,就可以从类外部访问 internal 属性和方法。
当您定义一个类时,它是公共可见的,可以被任何导入它的包访问,这意味着它默认是公共的,除非您指定可见性修饰符。类似地,当您在类中定义或声明属性和方法时,默认情况下可以通过类对象在类外部访问它们。为代码定义适当的可见性至关重要,主要是为了隐藏其他类不需要访问的属性和方法。
例如,考虑汽车如何供驾驶员使用。组成汽车的零件以及汽车内部如何工作的具体细节默认是隐藏的。汽车旨在尽可能直观地操作。您不会希望一辆汽车像商用飞机一样复杂,这类似于您不希望其他开发人员或未来的您对类中的哪些属性和方法应该使用感到困惑。
可见性修饰符可帮助您向项目中的其他类展示代码的相关部分,并确保实现不会被无意中使用,从而使代码易于理解且不易出错。
可见性修饰符应在声明类、方法或属性时放在声明语法之前,如此图所示:
指定属性的可见性修饰符
指定属性可见性修饰符的语法以 private
、protected
或 internal
修饰符开头,后跟定义属性的语法。您可以在此图表中看到语法。
例如,您可以在此代码片段中看到如何将 deviceStatus
属性设为私有:
open class SmartDevice(val name: String, val category: String) {
...
private var deviceStatus = "online"
...
}
您还可以为 setter 函数设置可见性修饰符。修饰符放在 set
关键字之前。您可以在此图表中看到语法。
对于 SmartDevice
类,应通过类对象在类外部读取 deviceStatus
属性的值。然而,只有类及其子类才能更新或写入该值。要实现此要求,您需要对 deviceStatus
属性的 set()
函数使用 protected
修饰符。
在 deviceStatus
属性的 set()
函数上使用 protected
修饰符
- 在
SmartDevice
超类的deviceStatus
属性中,向set()
函数添加protected
修饰符。
open class SmartDevice(val name: String, val category: String) {
...
var deviceStatus = "online"
protected set(value) {
field = value
}
...
}
您未在 set()
函数中执行任何操作或检查。您只是将 value
参数分配给 field
变量。正如您之前所学,这类似于属性 setter 的默认实现。在这种情况下,您可以省略 set()
函数的括号和主体。
open class SmartDevice(val name: String, val category: String) {
...
var deviceStatus = "online"
protected set
...
}
- 在
SmartHome
类中,定义一个deviceTurnOnCount
属性,将其设置为0
值,并带有一个 private setter 函数。
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
var deviceTurnOnCount = 0
private set
...
}
- 将
deviceTurnOnCount
属性后跟++
算术运算符添加到turnOnTv()
和turnOnLight()
方法中,然后将deviceTurnOnCount
属性后跟--
算术运算符添加到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()
}
...
}
方法的可见性修饰符
指定方法可见性修饰符的语法以 private
、protected
或 internal
修饰符开头,后跟定义方法的语法。您可以在此图表中看到语法。
例如,您可以在此代码片段中看到如何在 SmartTvDevice
类中为 nextChannel()
方法指定 protected
修饰符。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
...
protected fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
...
}
构造函数的可见性修饰符
指定构造函数可见性修饰符的语法类似于定义主构造函数,但有几个不同之处:
- 修饰符指定在类名之后,但在
constructor
关键字之前。 - 如果您需要为主构造函数指定修饰符,即使没有参数,也必须保留
constructor
关键字和括号。
您可以在此图表中看到语法。
例如,您可以在此代码片段中看到如何在 SmartDevice
构造函数中添加 protected
修饰符。
open class SmartDevice protected constructor (val name: String, val category: String) {
...
}
类的可见性修饰符
指定类可见性修饰符的语法以 private
、protected
或 internal
修饰符开头,后跟定义类的语法。您可以在此图表中看到语法。
例如,您可以在此代码片段中看到如何在 SmartDevice
类中指定 internal
修饰符。
internal open class SmartDevice(val name: String, val category: String) {
...
}
理想情况下,您应尽量严格控制属性和方法的可见性,因此应尽可能频繁地使用 private
修饰符声明它们。如果不能保持 private,请使用 protected
修饰符。如果不能保持 protected,请使用 internal
修饰符。如果不能保持 internal,请使用 public
修饰符。
指定适当的可见性修饰符
此表可帮助您根据类或构造函数的属性或方法应在哪里可访问来确定适当的可见性修饰符。
修饰符 | 在同一类中可访问 | 在子类中可访问 | 在同一模块中可访问 | 在模块外部可访问 |
| ✔ | 🗙 | 🗙 | 🗙 |
| ✔ | ✔ | 🗙 | 🗙 |
| ✔ | ✔ | ✔ | 🗙 |
| ✔ | ✔ | ✔ | ✔ |
在 SmartTvDevice
子类中,您不应允许从类外部控制 speakerVolume
和 channelNumber
属性。这些属性应仅通过 increaseSpeakerVolume()
和 nextChannel()
方法控制。
类似地,在 SmartLightDevice
子类中,brightnessLevel
属性应仅通过 increaseLightBrightness()
方法控制。
为 SmartTvDevice
和 SmartLightDevice
子类添加适当的可见性修饰符。
- 在
SmartTvDevice
类中,为speakerVolume
和channelNumber
属性添加private
可见性修饰符。
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
}
}
...
}
- 在
SmartLightDevice
类中,为brightnessLevel
属性添加private
修饰符。
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. 定义属性委托
您在上一节中学到,Kotlin 中的属性使用field
标识符来引用它。
当您查看目前的代码时,您可以看到 SmartTvDevice
和 SmartLightDevice
类中 speakerVolume
、channelNumber
和 brightnessLevel
属性的重复代码,用于检查值是否在范围内。您可以使用
创建属性委托的语法以变量声明开始,后跟 by
关键字以及处理属性的 getter 和 setter 函数的委托对象。您可以在此图表中看到语法。
在实现您可以委托实现的类之前,您需要熟悉
例如,在您建造房子之前,您告诉建筑师您想要什么。您想要一间卧室、儿童房、客厅、厨房和几个浴室。简而言之,您指定了
您已经学习了如何ReadWriteProperty
接口做类似的事情来创建委托。您将在下一个单元中了解更多关于接口的内容。
要为 var
类型创建委托类,您需要实现 ReadWriteProperty
接口。类似地,您需要为 val
类型实现 ReadOnlyProperty
接口。
为 var
类型创建委托
- 在
main()
函数之前,创建一个实现ReadWriteProperty<Any?,
Int>
接口的RangeRegulator
类。
class RangeRegulator() : ReadWriteProperty<Any?, Int> {
}
fun main() {
...
}
不用担心尖括号或其中的内容。它们表示泛型类型,您将在下一个单元中了解它们。
- 在
RangeRegulator
类的主构造函数中,添加一个initialValue
参数,一个 privateminValue
属性,以及一个 privatemaxValue
属性,所有这些属性的类型都是Int
。
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
}
- 在
RangeRegulator
类的主体中,重写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) {
}
}
这些方法充当属性的 getter 和 setter 函数。
- 在
SmartDevice
类之前的行上,导入ReadWriteProperty
和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) {
}
}
...
- 在
RangeRegulator
类中,在getValue()
方法之前的行上,定义一个fieldData
属性,并使用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) {
}
}
此属性充当变量的
- 在
getValue()
方法的主体中,返回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) {
}
}
- 在
setValue()
方法的主体中,在将value
参数赋值给fieldData
属性之前,检查分配的值是否在minValue..maxValue
范围内。
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
}
}
}
- 在
SmartTvDevice
类中,使用委托类定义speakerVolume
和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)
...
}
- 在
SmartLightDevice
类中,使用委托类定义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. 测试解决方案
您可以在此代码片段中看到解决方案代码。
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()
}
输出如下:
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. 尝试此挑战
- 在
SmartDevice
类中,定义一个printDeviceInfo()
方法,该方法打印字符串"Device
name:
$name,
category:
$category,
type:
$deviceType"
。 - 在
SmartTvDevice
类中,定义一个decreaseVolume()
方法,该方法减小音量,并定义一个previousChannel()
方法,该方法切换到上一个频道。 - 在
SmartLightDevice
类中,定义一个decreaseBrightness()
方法,该方法减小亮度。 - 在
SmartHome
类中,确保只有当每个设备的deviceStatus
属性设置为字符串 "on
" 时,才能执行所有操作。此外,确保deviceTurnOnCount
属性已正确更新。
完成实现后
- 在
SmartHome
类中,定义decreaseTvVolume()
、changeTvChannelToPrevious()
、printSmartTvInfo()
、printSmartLightInfo()
和decreaseLightBrightness()
方法。 - 在
SmartHome
类中调用SmartTvDevice
和SmartLightDevice
类中适当的方法。 - 在
main()
函数中,调用这些添加的方法进行测试。
12. 结论
恭喜!您学习了如何定义类和实例化对象。您还学习了如何创建类之间的关系和创建属性委托。
总结
- OOP 有四个主要原则:封装、抽象、继承和多态。
- 类使用
class
关键字定义,并包含属性和方法。 - 属性类似于变量,只是属性可以有自定义的 getter 和 setter。
- 构造函数指定如何实例化类的对象。
- 定义主构造函数时可以省略
constructor
关键字。 - 继承使代码重用更容易。
- IS-A 关系指继承。
- HAS-A 关系指组合。
- 可见性修饰符在实现封装方面发挥重要作用。
- Kotlin 提供四种可见性修饰符:
public
、private
、protected
和internal
修饰符。 - 属性委托允许您在多个类中重用 getter 和 setter 代码。