1. 准备工作
在本 Codelab 中,您将学习如何在 Kotlin 中使用类和对象。
类提供了用于构造对象的蓝图。对象是类的实例,其中包含相应对象的专属数据。对象和类实例可互换使用。
打个比方,假设您要建造一栋房子。“类”就好比建筑师的设计方案(也称为蓝图)。蓝图不是真正的房子,而是关于如何建造房子的说明。房子是根据蓝图建造的实际事物或物体。
就像房屋蓝图规划了多个房间,而每个房间都有自己的设计和用途一样,每个类也都有着各自的设计和用途。若要了解如何设计类,您必须熟悉面向对象的编程 (OOP),通过该框架学习如何将数据、逻辑和行为放入对象中。
OOP 可帮助您将复杂的实际问题简化为较小的对象。OOP 涵盖以下四个基本概念,您会在本 Codelab 后面的部分中详细了解各个概念:
- 封装:将相关属性和针对这些属性执行操作的方法封装在类中。以手机为例,它封装了摄像头、显示屏、存储卡以及其他一些硬件和软件组件。您不必担心这些组件的内部连接方式。
- 抽象:封装的扩展,其目的是尽可能隐藏内部实现逻辑。例如,如果您要使用手机拍照,只需打开相机应用,将手机对准要拍摄的场景,然后点击按钮即可。您不需要了解相机应用的构建方式或手机上相机硬件的实际运作方式。简而言之,相机应用的内部机制以及移动设备相机的拍照方式已经过抽象,可让您专心执行重要的任务。
- 继承:可让您通过建立父子关系来基于其他类的特性和行为构建类。例如,不同的制造商生产各种运行 Android OS 的移动设备,但每种设备的界面都不同。换言之,制造商会继承 Android 操作系统的功能,并在这个基础上构建自己的自定义功能。
- 多态性:Polymorphism(多态性)这个单词是希腊语词根“poly-”(意为许多)和“morphism”(意为形态)的合成词。多态性是指以单一、通用的方式使用不同对象的能力。例如,当您将蓝牙音箱连接到手机后,手机只需要知道目前有设备可通过蓝牙播放音频。虽然可供您选择的蓝牙音箱有很多种,但手机不必知道各个音箱的具体使用方式。
最后,您将了解属性委托,它们提供的可重复使用的代码能让您用简洁的语法来管理属性值。在本 Codelab 中,您将构建智能家居应用的类结构,并在构建过程中学习这些概念。
前提条件
- 了解如何在 Kotlin 园地中打开、修改和运行代码。
- 了解 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 园地中,将内容替换为空的
main()
函数:
fun main() {
}
- 在
main()
函数前面的代码行上,定义一个主体包含//
empty
body
注释的SmartDevice
类:
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()
函数表示开关该设备的操作(分别用于实现开启和关闭行为)。
在类中定义函数的语法与您之前学习的语法相同。唯一的区别在于,该函数是放在类主体中。在类主体中定义函数时,该函数称为成员函数或方法,用于表示类的行为。在本 Codelab 的剩余部分中,出现在类主体内的函数一律称为方法。
在 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()
方法之后,添加用于输出"Smart
device
is
turned
off."
字符串的turnOff()
方法:
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.")
}
}
- 在
smartDevice
变量后面的代码行上,调用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 中,所有类默认都是最终类,也就是说您无法扩展这些类,因此必须定义类之间的关系。
定义 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
}
}
}
- 定义被赋予
1
值的channelNumber
属性,并包含指定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
}
}
}
- 定义会调高音量并输出
"Speaker
volume
increased
to
$speakerVolume."
字符串的increaseSpeakerVolume()
方法:
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.")
}
}
- 添加会增加频道号并输出
"Channel
number
increased
to
$channelNumber."
字符串的nextChannel()
方法:
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
子类主体中,定义被赋予0
值的brightnessLevel
属性,并包含指定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
}
}
}
- 定义会调高灯具亮度并输出
"Brightness
increased
to
$brightnessLevel."
字符串的increaseBrightness()
方法:
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
类的主体中,定义会对smartTvDevice
属性调用turnOn()
方法的turnOnTv()
方法:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
}
- 在
turnOnTv()
方法之后的代码行上,定义会对smartTvDevice
属性调用turnOff()
方法的turnOffTv()
方法:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
}
- 在
turnOffTv()
方法后面的代码行上,定义会对smartTvDevice
属性调用increaseSpeakerVolume()
方法的increaseTvVolume()
方法,然后定义会对smartTvDevice
属性调用nextChannel()
方法的changeTvChannelToNext()
方法:
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
属性:
// Smart Home HAS-A smart TV device and smart light.
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
}
- 在
SmartHome
主体中,定义会对smartLightDevice
对象调用turnOn()
方法的turnOnLight()
方法,以及会对smartLightDevice
对象调用turnOff()
方法的turnOffLight()
方法:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
smartLightDevice.turnOn()
}
fun turnOffLight() {
smartLightDevice.turnOff()
}
}
- 在
turnOffLight()
方法后面的代码行上,定义会对smartLightDevice
属性调用increaseBrightness()
方法的increaseLightBrightness()
方法:
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()
方法后面的代码行上,定义会调用turnOffTv()
和turnOffLight()
方法的turnOffAllDevices()
方法:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun turnOffAllDevices() {
turnOffTv()
turnOffLight()
}
}
替换子类中的父类方法
如前所述,虽然所有智能设备都支持开启和关闭功能,但它们的执行方式各有不同。如需提供这种特定于设备的行为,您需要替换父类中定义的 turnOn()
和 turnOff()
方法。替换意味着要拦截操作,通常是手动控制。替换方法时,子类中的方法会中断父类中定义的方法的执行,并提供其自有的执行内容。
替换 SmartDevice
类中的 turnOn()
和 turnOff()
方法:
- 在
SmartDevice
父类主体中,找到每个方法的fun
关键字,并在前面添加open
关键字:
open class SmartDevice {
...
var deviceStatus = "online"
open fun turnOn() {
// function body
}
open fun turnOff() {
// function body
}
}
- 在
SmartLightDevice
子类中,找到turnOn()
和turnOff()
方法的fun
关键字,并在前面添加override
关键字:
class SmartLightDevice(name: String, category: String) :
SmartDevice(name = name, category = category) {
var brightnessLevel = 0
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")
}
fun increaseBrightness() {
brightnessLevel++
}
}
override
关键字会告知 Kotlin 运行时去执行子类所定义方法中包含的代码。
- 在
SmartTvDevice
类中,找到turnOn()
和turnOff()
方法的fun
关键字,并在前面添加override
关键字:
class SmartTvDevice(name: String, category: String) :
SmartDevice(name = name, category = category) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
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")
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
}
- 在
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 is turned on. The brightness level is set to 2.
这是一个关于多态性的示例。代码会对 SmartDevice
类型的变量调用 turnOn()
方法,并能根据变量的实际值来执行 turnOn()
方法的不同实现。
使用 super
关键字在子类中重复使用父类代码
如果仔细观察 turnOn()
和 turnOff()
方法,您就会发现,每次在 SmartTvDevice
和 SmartLightDevice
子类中调用方法时,更新 deviceStatus
变量的方式都类似,这是因为有重复的代码。因此,在更新 SmartDevice
类中的状态时,您可以重复使用该代码。
若要调用父类中遭到替换的方法,您需要使用 super
关键字。从父类调用方法与从类外部调用该方法类似。请不要在对象和方法之间使用 .
运算符,而是使用 super
关键字,后者会告知 Kotlin 编译器对父类(而不是子类)调用方法。
从父类调用方法的语法是以 super
关键字开头,后面依次跟 .
运算符、函数名称和一对圆括号。可视情况在圆括号中包含相应实参。语法如下图所示:
重复使用 SmartDevice
父类中的代码:
- 将可重复使用的代码从
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(name: String, category: String) :
SmartDevice(name = name, category = category) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
override fun turnOn() {
super.turnOn()
println("Smart TV turned on. Speaker volume set to $speakerVolume.")
}
override fun turnOff() {
super.turnOff()
println("Smart TV turned off")
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
}
fun previousChannel() {
channelNumber--
}
}
class SmartLightDevice(name: String, category: String) :
SmartDevice(name = name, category = category) {
var brightnessLevel = 0
override fun turnOn() {
super.turnOn()
brightnessLevel = 2
println("Smart Light turned on. The brightness level is set to $brightnessLevel.")
}
override fun turnOff() {
super.turnOff()
brightnessLevel = 0
println("Smart Light turned off")
}
fun increaseBrightness() {
brightnessLevel++
}
}
替换子类中的父类属性
与方法类似,您也可以使用相同的步骤替换属性。
替换 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
:可让系统在相同模块中访问声明。internal 修饰符与 private 类似,但您可以从类的外部访问内部属性和方法,只要是在相同模块中进行访问即可。
定义相应类后,该类便会公开显示,并可供所有导入该类的软件包访问;也就是说,除非您指定可见性修饰符,否则该类默认处于公开状态。同样,在类中定义或声明属性和方法后,默认情况下,您可以通过类对象在类的外部访问它们。请务必为代码定义适当的可见性,这主要是为了隐藏其他类不需要访问的属性和方法。
例如,想一想驾驶员是如何操控汽车的。默认情况下,我们会隐藏汽车各个零部件的具体细节以及汽车的内部运行方式。汽车的设计理念是提供尽可能直观的操控方式。您肯定不希望汽车的操控方式像商用飞机那样复杂,就如同您不希望其他开发者或未来的自己对要使用类的哪些属性和方法感到困惑。
可见性修饰符可协助您将代码的相关部分提供给项目中的其他类使用,并确保实现不会在无意间遭到使用,从而让代码易于理解且不易出错。
在声明类、方法或属性时,应将可见性修饰符放在声明语法之前,如下图所示:
为属性指定可见性修饰符
为属性指定可见性修饰符的语法是以 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
属性中,将protected
修饰符添加到set()
函数中:
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
,并包含私有 setter 函数:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
var deviceTurnOnCount = 0
private set
...
}
- 在
turnOnTv()
和turnOnLight()
方法中添加后跟++
算术运算符的deviceTurnOnCount
属性,然后在turnOffTv()
和turnOffLight()
方法中添加后跟--
算术运算符的deviceTurnOnCount
属性:
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++
}
...
}
为构造函数指定可见性修饰符
为构造函数指定可见性修饰符的语法与定义主要构造函数的语法类似,但存在以下两点差异:
- 修饰符是在类名称之后、
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
子类中,应仅通过 increaseLightBrightness()
方法来控制 brightnessLevel
属性。
在 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
形参是否处于minValue..maxValue
范围内,然后再将其赋给fieldData
属性:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return fieldData
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
if (value in minValue..maxValue) {
fieldData = value
}
}
}
- 在
SmartTvDevice
类中,使用委托类来定义speakerVolume
和channelNumber
属性:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
private var speakerVolume by RangeRegulator(initialValue = 0, 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) {
private var brightnessLevel by RangeRegulator(initialValue = 2, 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)
override fun turnOn() {
super.turnOn()
println(
"$name is turned on. Speaker volume set to $speakerVolume and channel number is " +
"set to $channelNumber."
)
}
override fun turnOff() {
super.turnOff()
println("$name turned off")
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
}
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart Light"
private var brightnessLevel by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)
override fun turnOn() {
super.turnOn()
brightnessLevel = 2
println("$name is turned on. The brightness level is $brightnessLevel.")
}
override fun turnOff() {
super.turnOff()
brightnessLevel = 0
println("$name turned off")
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
}
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> {
private 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() {
val smartHome = SmartHome(
SmartTvDevice(deviceName = "Android TV", deviceCategory = "Entertainment"),
SmartLightDevice(deviceName = "Google light", deviceCategory = "Utility")
)
smartHome.turnOnTv()
smartHome.turnOnLight()
println("Total number of devices currently turned on: ${smartHome.deviceTurnOnCount}")
println()
smartHome.increaseTvVolume()
smartHome.changeTvChannelToNext()
smartHome.increaseLightBrightness()
println()
smartHome.turnOffAllDevices()
println("Total number of devices currently turned on: ${smartHome.deviceTurnOnCount}.")
}
输出如下所示:
Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1. Google Light is turned on. The brightness level is 2. Total number of devices currently turned on: 2. Speaker volume increased to 3. Channel number increased to 2. Brightness increased to 3. Android TV turned off. Google light turned off. Total number of devices currently turned on: 0.
11. 尝试新的挑战
- 在
SmartDevice
类中,定义一个可输出"Device
name:
$name,
category:
$category,
type:
$deviceType"
字符串的printDeviceInfo()
方法。 - 在
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 代码。