1. 开始之前
本代码实验室将教你如何在 Kotlin 中使用类和对象。
类提供蓝图,从中可以构造对象。对象是类的实例,包含特定于该对象的数据。您可以互换使用对象或类实例。
打个比方,假设您正在建造一栋房子。类类似于建筑师的设计图纸,也称为蓝图。蓝图不是房子本身;它是建造房子的说明。房子是实际的东西,或者说是根据蓝图建造的对象。
就像房屋蓝图指定多个房间,每个房间都有自己的设计和用途一样,每个类都有自己的设计和用途。要了解如何设计类,您需要熟悉*面向对象编程 (OOP)*,这是一种教你将数据、逻辑和行为封装在对象中的框架。
OOP 帮助您将复杂的现实世界问题简化为更小的对象。OOP 有四个基本概念,您将在本代码实验室的后面部分学习到更多相关内容。
- **封装。**将相关的属性和对这些属性执行操作的方法封装到一个类中。例如,考虑您的手机。它封装了摄像头、显示屏、存储卡以及其他一些硬件和软件组件。您不必担心组件内部是如何连接的。
- **抽象。**封装的扩展。其思想是尽可能隐藏内部实现逻辑。例如,要使用手机拍照,您只需打开相机应用程序,将手机指向您要拍摄的场景,然后点击按钮即可拍摄照片。您无需了解相机应用程序是如何构建的,也不需要了解手机上的相机硬件是如何工作的。简而言之,相机应用程序的内部机制以及手机相机如何拍摄照片都被抽象化,让您可以执行重要的任务。
- **继承。**使您能够通过建立父子关系,在一个类的基础上构建其他类的特性和行为。例如,有不同的制造商生产各种运行 Android 操作系统的移动设备,但每种设备的用户界面都不同。换句话说,制造商继承了 Android 操作系统功能,并在其之上构建了自己的定制功能。
- **多态。**这个词是希腊词根*poly-*(意思是“多”)和*-morphism*(意思是“形式”)的组合。多态性是指能够以单一通用方式使用不同对象的能力。例如,当您将蓝牙扬声器连接到手机时,手机只需要知道有一个设备可以通过蓝牙播放音频。但是,您可以选择各种蓝牙扬声器,而您的手机不需要知道如何与每个扬声器具体配合使用。
最后,您将了解属性委托,它提供可重用的代码,可以使用简洁的语法来管理属性值。在本代码实验室中,您将在构建智能家居应用程序的类结构时学习这些概念。
先决条件
- 如何在 Kotlin Playground 中打开、编辑和运行代码。
- Kotlin 编程基础知识,包括变量、函数以及
println()
和main()
函数
您将学习的内容
- OOP 的概述。
- 什么是类。
- 如何使用构造函数、函数和属性定义类。
- 如何实例化对象。
- 什么是继承。
- IS-A 和 HAS-A 关系之间的区别。
- 如何重写属性和函数。
- 什么是可见性修饰符。
- 什么是委托以及如何使用
by
委托。
您将构建的内容
- 智能家居类结构。
- 表示智能设备的类,例如智能电视和智能灯。
您需要的内容
- 一台具有互联网访问权限的电脑和一个网络浏览器
2. 定义一个类
定义类时,您将指定该类的所有对象都应具有的属性和方法。
类定义以class
关键字开头,后跟一个名称和一对花括号。开花括号之前的语法部分也称为类头。在花括号中,您可以为类指定属性和函数。您很快就会了解属性和函数。您可以在此图中看到类定义的语法。
以下是推荐的类命名约定。
- 您可以选择任何您想要的类名,但不要使用 Kotlin 关键字作为类名,例如
fun
关键字。 - 类名使用 PascalCase 编写,因此每个单词都以大写字母开头,单词之间没有空格。例如,在*S*martDevice 中,每个单词的第一个字母都大写,单词之间没有空格。
一个类由三个主要部分组成。
- **属性。**指定类对象的属性的变量。
- **方法。**包含类行为和操作的函数。
- **构造函数。**一个特殊的成员函数,用于在定义它的程序中创建类的实例。
这并不是您第一次使用类。在之前的代码实验室中,您学习了数据类型,例如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()
方法
- 在
smartTvDevice
变量后面的main()
函数行中,调用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. 定义类属性
在第一单元中,您学习了变量,它们是单个数据片段的容器。您学习了如何使用 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,您可以编写一个 _setter_ 函数。当您更新属性的值时,您需要检查该值是否在 0 到 100 的范围内。另一个例子是,假设有一个要求确保名称始终大写。您可以实现一个 _getter_ 函数将 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()
函数中更新值,您需要使用属性的 _支持字段_。它由 Kotlin 编译器自动生成,并用 field
标识符引用。
例如,当您想要在 set()
函数中更新属性的值时,您可以使用 set()
函数的参数(称为 value
参数),并将其赋值给 field
变量,如以下代码片段所示
var speakerVolume = 2
set(value) {
field = value
}
例如,为了确保赋值给 speakerVolume
属性的值在 0 到 100 的范围内,您可以实现 _setter_ 函数,如以下代码片段所示
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
set()
函数使用 in
关键字后跟值范围来检查 Int
值是否在 0 到 100 的范围内。如果值在预期范围内,则更新 field
值。否则,属性的值保持不变。
您将在本 codelab 的“实现类之间的关系”部分将此属性包含在类中,因此您现在不需要向代码中添加 setter 函数。
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
类型的状态码来指示初始设备状态。如果设备处于离线状态,API 返回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
值,并使用一个setter函数指定0..200
范围。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
}
- 定义一个
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
值,并使用一个setter函数指定0..100
范围。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
}
- 定义一个
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关系中建立两个类之间的关系。对象也是它继承的类的实例。在HAS-A关系中,对象可以拥有另一个类的实例,而实际上并不是该类的实例本身。您可以在此图中看到这些关系的高级表示。
IS-A **关系**
当您在SmartDevice
超类和SmartTvDevice
子类之间指定IS-A关系时,这意味着SmartDevice
超类可以执行的操作,SmartTvDevice
子类也可以执行。这种关系是单向的,所以您可以说每台智能电视都是智能设备,但您不能说每台智能设备都是智能电视。IS-A关系的代码表示在此代码片段中显示。
// Smart TV IS-A smart device.
class SmartTvDevice : SmartDevice() {
}
不要仅为了实现代码可重用性而使用继承。在您决定之前,请检查这两个类是否彼此相关。如果它们表现出某种关系,请检查它们是否真的符合IS-A关系。问问自己:“我可以说子类是超类吗?”例如,Android *是一个*操作系统。
HAS-A **关系**
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
变量,该变量实例化一个接受"Android TV"
参数和"Entertainment"
参数的SmartTvDevice
对象。
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
变量以实例化一个接受"Google Light"
参数和"Utility"
参数的SmartLightDevice
类,然后在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"
}
}
- 使用
super
关键字在SmartTvDevice
和SmartLightDevice
子类中从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
关键字定义一个设置为"unknown"
字符串的deviceType
属性。
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open val deviceType = "unknown"
...
}
- 在
SmartTvDevice
类中,使用override
和val
关键字定义一个设置为"Smart TV"
字符串的deviceType
属性。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart TV"
...
}
- 在
SmartLightDevice
类中,使用override
和val
关键字定义一个设置为"Smart Light"
字符串的deviceType
属性。
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
. 使声明在同一模块内可访问。`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
类中,定义一个值为0
的deviceTurnOnCount
属性,并使用私有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
修饰符声明它们。如果无法将其设为私有,请使用protected
修饰符。如果无法将其设为受保护的,请使用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
属性的值是否在范围内的重复代码。您可以使用委托在setter函数中重用范围检查代码。与其使用字段以及getter和setter函数来管理值,不如由委托来管理。
创建属性委托的语法以变量声明开头,后跟by
关键字,以及处理属性的getter和setter函数的委托对象。您可以在此图中看到语法
在实现可以委托实现的类之前,您需要熟悉接口。接口是一个契约,实现它的类需要遵守该契约。它关注的是做什么而不是如何做。简而言之,接口可以帮助您实现抽象。
例如,在建造房屋之前,您会告诉建筑师您想要什么。您想要一个卧室、儿童房、客厅、厨房和几个浴室。简而言之,您指定您想要什么,而建筑师则指定如何实现它。您可以在此图中看到创建接口的语法
您已经了解了如何扩展类和重写其功能。对于接口,类实现接口。该类为接口中声明的方法和属性提供实现细节。您将对ReadWriteProperty
接口执行类似的操作以创建委托。您将在下一单元中学习有关接口的更多信息。
要为var
类型创建委托类,您需要实现ReadWriteProperty
接口。类似地,您需要为val
类型实现ReadOnlyProperty
接口。
创建var
类型的委托
- 在
main()
函数之前,创建一个实现ReadWriteProperty<Any?,
Int>
接口的RangeRegulator
类
class RangeRegulator() : ReadWriteProperty<Any?, Int> {
}
fun main() {
...
}
不用担心尖括号或其中的内容。它们表示泛型类型,您将在下一单元中学习有关它们的信息。
- 在
RangeRegulator
类的主要构造函数中,添加一个initialValue
参数、一个私有的minValue
属性和一个私有的maxValue
属性,它们都是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. 结论
恭喜!您学习了如何定义类和实例化对象。您还学习了如何在类之间创建关系以及创建属性委托。
总结
- 面向对象编程的四个主要原则是:封装、抽象、继承和多态。
- 类是用
class
关键字定义的,包含属性和方法。 - 属性类似于变量,不同的是属性可以具有自定义的getter和setter。
- 构造函数指定如何实例化类的对象。
- 定义主构造函数时,可以省略
constructor
关键字。 - 继承使代码重用更容易。
- IS-A关系指的是继承。
- HAS-A关系指的是组合。
- 可见性修饰符在实现封装中起着重要作用。
- Kotlin 提供四种可见性修饰符:
public
、private
、protected
和internal
修饰符。 - 属性委托允许您在多个类中重用getter和setter代码。