在 Kotlin 中使用类和对象

1. 开始之前

本代码实验室将教你如何在 Kotlin 中使用类和对象。

类提供蓝图,从中可以构造对象。对象是类的实例,包含特定于该对象的数据。您可以互换使用对象或类实例。

打个比方,假设您正在建造一栋房子。类类似于建筑师的设计图纸,也称为蓝图。蓝图不是房子本身;它是建造房子的说明。房子是实际的东西,或者说是根据蓝图建造的对象。

就像房屋蓝图指定多个房间,每个房间都有自己的设计和用途一样,每个类都有自己的设计和用途。要了解如何设计类,您需要熟悉*面向对象编程 (OOP)*,这是一种教你将数据、逻辑和行为封装在对象中的框架。

OOP 帮助您将复杂的现实世界问题简化为更小的对象。OOP 有四个基本概念,您将在本代码实验室的后面部分学习到更多相关内容。

  • **封装。**将相关的属性和对这些属性执行操作的方法封装到一个类中。例如,考虑您的手机。它封装了摄像头、显示屏、存储卡以及其他一些硬件和软件组件。您不必担心组件内部是如何连接的。
  • **抽象。**封装的扩展。其思想是尽可能隐藏内部实现逻辑。例如,要使用手机拍照,您只需打开相机应用程序,将手机指向您要拍摄的场景,然后点击按钮即可拍摄照片。您无需了解相机应用程序是如何构建的,也不需要了解手机上的相机硬件是如何工作的。简而言之,相机应用程序的内部机制以及手机相机如何拍摄照片都被抽象化,让您可以执行重要的任务。
  • **继承。**使您能够通过建立父子关系,在一个类的基础上构建其他类的特性和行为。例如,有不同的制造商生产各种运行 Android 操作系统的移动设备,但每种设备的用户界面都不同。换句话说,制造商继承了 Android 操作系统功能,并在其之上构建了自己的定制功能。
  • **多态。**这个词是希腊词根*poly-*(意思是“多”)和*-morphism*(意思是“形式”)的组合。多态性是指能够以单一通用方式使用不同对象的能力。例如,当您将蓝牙扬声器连接到手机时,手机只需要知道有一个设备可以通过蓝牙播放音频。但是,您可以选择各种蓝牙扬声器,而您的手机不需要知道如何与每个扬声器具体配合使用。

最后,您将了解属性委托,它提供可重用的代码,可以使用简洁的语法来管理属性值。在本代码实验室中,您将在构建智能家居应用程序的类结构时学习这些概念。

先决条件

  • 如何在 Kotlin Playground 中打开、编辑和运行代码。
  • Kotlin 编程基础知识,包括变量、函数以及println()main()函数

您将学习的内容

  • OOP 的概述。
  • 什么是类。
  • 如何使用构造函数、函数和属性定义类。
  • 如何实例化对象。
  • 什么是继承。
  • IS-A 和 HAS-A 关系之间的区别。
  • 如何重写属性和函数。
  • 什么是可见性修饰符。
  • 什么是委托以及如何使用by委托。

您将构建的内容

  • 智能家居类结构。
  • 表示智能设备的类,例如智能电视和智能灯。

您需要的内容

  • 一台具有互联网访问权限的电脑和一个网络浏览器

2. 定义一个类

定义类时,您将指定该类的所有对象都应具有的属性和方法。

类定义以class关键字开头,后跟一个名称和一对花括号。开花括号之前的语法部分也称为类头。在花括号中,您可以为类指定属性和函数。您很快就会了解属性和函数。您可以在此图中看到类定义的语法。

It starts with class keyword followed by name, set of opening and closing curly braces. The curly braces contain the body of class which describes its blue print.

以下是推荐的类命名约定。

  • 您可以选择任何您想要的类名,但不要使用 Kotlin 关键字作为类名,例如fun关键字。
  • 类名使用 PascalCase 编写,因此每个单词都以大写字母开头,单词之间没有空格。例如,在*S*martDevice 中,每个单词的第一个字母都大写,单词之间没有空格。

一个类由三个主要部分组成。

  • **属性。**指定类对象的属性的变量。
  • **方法。**包含类行为和操作的函数。
  • **构造函数。**一个特殊的成员函数,用于在定义它的程序中创建类的实例。

这并不是您第一次使用类。在之前的代码实验室中,您学习了数据类型,例如IntFloatStringDouble数据类型。这些数据类型在 Kotlin 中定义为类。当您如以下代码片段所示定义变量时,您将创建一个Int类的对象,该对象使用1值实例化。

val number: Int = 1

定义一个SmartDevice

  1. Kotlin Playground 中,将内容替换为空的main()函数。
fun main() {
}
  1. main()函数之前的行上,定义一个SmartDevice类,其主体包含一个* // *empty *body*注释。
class SmartDevice {
    // empty body
}

fun main() {
}

3. 创建类的实例

正如您所了解的,类是对象的蓝图。Kotlin 运行时使用类或蓝图来创建特定类型的对象。使用SmartDevice类,您就拥有了智能设备的蓝图。要在程序中拥有*实际的*智能设备,您需要创建一个SmartDevice对象实例。实例化语法以类名开头,后跟一对括号,如您在此图中所见。

1d25bc4f71c31fc9.png

要使用对象,您需要创建对象并将其分配给变量,这与定义变量的方式类似。您使用val关键字创建不可变变量,使用var关键字创建可变变量。valvar关键字后跟变量名,然后是=赋值运算符,然后是类对象的实例化。您可以在此图中看到语法。

f58430542f2081a9.png

SmartDevice类实例化为对象

  • main()函数中,使用val关键字创建一个名为smartTvDevice的变量,并将其初始化为SmartDevice类的实例。
fun main() {
    val smartTvDevice = SmartDevice()
}

4. 定义类方法

在单元 1 中,您学习了

  • 函数的定义使用fun关键字,后跟一对括号和一对花括号。花括号包含代码,这些代码是执行任务所需的指令。
  • 调用函数会导致包含在该函数中的代码执行。

类可以执行的操作定义为类中的函数。例如,假设您拥有一个智能设备、一台智能电视或一盏智能灯,您可以用手机将其打开和关闭。智能设备在编程中转换为SmartDevice类,而打开和关闭操作则由turnOn()turnOff()函数表示,这些函数实现了打开和关闭行为。

在类中定义函数的语法与您之前学习的相同。唯一的区别在于该函数位于类体中。当您在类体中定义*函数*时,它被称为成员函数或*方法*,它表示类的行为。在本代码实验室的其余部分中,只要函数出现在类的体中,就将其称为方法。

SmartDevice类中定义turnOn()turnOff()方法

  1. SmartDevice类的体中,定义一个具有空体的turnOn()方法。
class SmartDevice {
    fun turnOn() {

    }
}
  1. turnOn()方法的体中,添加一个println()语句,然后传递一个"Smart device is turned on."字符串。
class SmartDevice {
    fun turnOn() {
        println("Smart device is turned on.")
    }
}
  1. 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()
        ...
    }

    ...
}

要在类外部调用类方法,请以类对象开头,后跟 . 运算符、函数名称和一组括号。如果适用,括号包含方法所需的参数。您可以在此图中看到语法

fc609c15952551ce.png

在对象上调用 turnOn()turnOff() 方法

  1. smartTvDevice 变量后面的 main() 函数行中,调用 turnOn() 方法
fun main() {
    val smartTvDevice = SmartDevice()
    smartTvDevice.turnOn()
}
  1. turnOn() 方法后面的行中,调用 turnOff() 方法
fun main() {
    val smartTvDevice = SmartDevice()
    smartTvDevice.turnOn()
    smartTvDevice.turnOff()
}
  1. 运行代码。

输出如下所示

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

5. 定义类属性

在第一单元中,您学习了变量,它们是单个数据片段的容器。您学习了如何使用 val 关键字创建只读变量,以及使用 var 关键字创建可变变量。

方法定义了类可以执行的操作,而属性则定义了类的特征或数据属性。例如,智能设备具有以下属性:

  • 名称。 设备的名称。
  • 类别。 智能设备的类型,例如娱乐、实用或烹饪。
  • 设备状态。 设备是打开、关闭、在线还是离线。当设备连接到互联网时,则认为它在线。否则,则认为它离线。

属性基本上是在类体而不是函数体中定义的变量。这意味着定义属性和变量的语法相同。您可以使用 val 关键字定义不可变属性,使用 var 关键字定义可变属性。

将上述特性实现为 SmartDevice 类的属性

  1. 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.")
    }
}
  1. 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.")
    }
}
  1. smartTvDevice 变量后面的行中,调用 println() 函数,然后将 "Device name is: ${smartTvDevice.name}" 字符串传递给它
fun main() {
    val smartTvDevice = SmartDevice()
    println("Device name is: ${smartTvDevice.name}")
    smartTvDevice.turnOn()
    smartTvDevice.turnOff()
}
  1. 运行代码。

输出如下所示

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() 函数。您可以在此图中看到语法

f2cf50a63485599f.png

当您没有为属性定义 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 类中,namecategory 属性是不可变的。您需要确保所有 SmartDevice 类的实例都初始化 namecategory 属性。使用当前实现,namecategory 属性的值是硬编码的。这意味着所有智能设备的名称都为 "Android TV" 字符串,类别为 "Entertainment" 字符串。

为了保持不变性但避免硬编码值,请使用参数化构造函数来初始化它们

  • SmartDevice 类中,将 namecategory 属性移动到构造函数中,而不赋值默认值
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.")
    }
}

构造函数现在接受参数来设置其属性,因此实例化此类对象的也发生了变化。您可以在此图中看到实例化对象的完整语法

bbe674861ec370b6.png

这是代码表示

SmartDevice("Android TV", "Entertainment")

构造函数的两个参数都是字符串。哪个参数应该赋值有点不清楚。为了解决这个问题,类似于传递函数参数的方式,您可以创建一个带有命名参数的构造函数,如本代码片段所示。

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

Kotlin 中主要有两种类型的构造函数。

  • 主构造函数。一个类只能有一个主构造函数,它定义为类头的一部分。主构造函数可以是默认构造函数或参数化构造函数。主构造函数没有函数体。这意味着它不能包含任何代码。
  • 次构造函数。一个类可以有多个次构造函数。您可以定义带有或不带参数的次构造函数。次构造函数可以初始化类,并具有函数体,函数体可以包含初始化逻辑。如果类具有主构造函数,则每个次构造函数都需要初始化主构造函数。

您可以使用主构造函数在类头中初始化属性。传递给构造函数的参数将赋值给属性。定义主构造函数的语法以类名开头,后跟constructor关键字和一组圆括号。圆括号包含主构造函数的参数。如果有多个参数,则用逗号分隔参数定义。您可以在此图中看到定义主构造函数的完整语法。

aa05214860533041.png

次构造函数包含在类的函数体中,其语法包括三个部分。

  • 次构造函数声明。次构造函数定义以constructor关键字后跟圆括号开头。如果适用,圆括号包含次构造函数所需的参数。
  • 主构造函数初始化。初始化以冒号开头,后跟this关键字和一组圆括号。如果适用,圆括号包含主构造函数所需的参数。
  • 次构造函数函数体。主构造函数的初始化后跟一对大括号,其中包含次构造函数的函数体。

您可以在此图中看到语法。

2dc13ef136009e98.png

例如,假设您想集成智能设备提供商开发的 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父类,并定义这些共同的属性和行为。然后,您可以创建子类,例如SmartTvDeviceSmartLightDevice类,它们继承父类的属性。

在编程术语中,我们说SmartTvDeviceSmartLightDevice扩展SmartDevice父类。父类也称为超类,子类也称为子类。您可以在此图中看到它们之间的关系。

Diagram representing inheritance relationship between classes.

但是,在 Kotlin 中,所有类默认都是 final 的,这意味着您不能扩展它们,因此您必须定义它们之间的关系。

定义SmartDevice超类及其子类之间的关系

  1. SmartDevice超类中,在class关键字之前添加open关键字以使其可扩展。
open class SmartDevice(val name: String, val category: String) {
    ...
}

open关键字通知编译器此类是可扩展的,因此现在其他类可以扩展它。

创建子类的语法以创建类头开头,就像您之前所做的那样。构造函数的右括号后跟一个空格、一个冒号、另一个空格、超类名称和一组圆括号。如有必要,圆括号包含超类构造函数所需的参数。您可以在此图中看到语法。

1ac63b66e6b5c224.png

  1. 创建一个扩展SmartDevice超类的SmartTvDevice子类。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {
}

SmartTvDeviceconstructor定义没有指定属性是可变的还是不可变的。这意味着deviceNamedeviceCategory参数仅仅是constructor参数而不是类属性。您将无法在类中使用它们,而只是将它们传递给超类构造函数。

  1. 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属性,将其赋值为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
            }
        }
}
  1. 定义一个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.")
    } 
}
  1. 添加一个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.")
    }
}
  1. SmartTvDevice子类之后,定义一个扩展SmartDevice超类的SmartLightDevice子类。
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {
}
  1. 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
            }
        }
}
  1. 定义一个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关系中,对象可以拥有另一个类的实例,而实际上并不是该类的实例本身。您可以在此图中看到这些关系的高级表示。

High level representation of HAS-A and IS-A relationship.

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

  1. SmartLightDevice类和main()函数之间,定义一个SmartHome
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    ...

}

class SmartHome {
}

fun main() { 
    ...
}
  1. SmartHome类构造函数中,使用val关键字创建一个SmartTvDevice类型的smartTvDevice属性。
// The SmartHome class HAS-A smart TV device.
class SmartHome(val smartTvDevice: SmartTvDevice) {

}
  1. SmartHome类的函数体中,定义一个turnOnTv()方法,该方法在smartTvDevice属性上调用turnOn()方法。
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }
}
  1. turnOnTv()方法之后,定义一个turnOffTv()方法,该方法在smartTvDevice属性上调用turnOff()方法。
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        smartTvDevice.turnOff()
    }

}
  1. 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()
    }
}
  1. SmartHome类的构造函数中,将smartTvDevice属性参数移动到单独一行,并在其后添加逗号。
class SmartHome(
    val smartTvDevice: SmartTvDevice,
) {

    ...

}
  1. smartTvDevice属性后一行,使用val关键字定义一个类型为SmartLightDevicesmartLightDevice属性。
// The SmartHome class HAS-A smart TV device and smart light.
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

}
  1. 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()
    }
}
  1. 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()
    }
}
  1. increaseLightBrightness()方法后一行,定义一个turnOffAllDevices()方法,该方法调用turnOffTv()turnOffLight()方法。
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

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

重写子类中的超类方法。

如前所述,尽管所有智能设备都支持开/关功能,但它们执行此功能的方式有所不同。为了提供这种设备特定的行为,您需要重写在超类中定义的turnOn()turnOff()方法。重写意味着拦截操作,通常是为了进行手动控制。当您重写一个方法时,子类中的方法会中断在超类中定义的方法的执行,并提供其自身的执行。

重写SmartDevice类的turnOn()turnOff()方法。

  1. 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
    }
}
  1. 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() {
    }
}
  1. 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.")
    }
  1. 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() {
    }
}
  1. 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")
    }
}
  1. 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运行时执行在子类中定义的方法中包含的代码。

  1. 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() {
    }
}
  1. 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."
        )
    }
}
  1. SmartTvDevice类的类体中,在turnOn()方法之后,定义一个具有空函数体的turnOff()方法。
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    fun turnOn() {
        ...
    }

    fun turnOff() {
    }
}
  1. 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")
    }
}
  1. 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")
    }
}
  1. main()函数中,使用var关键字定义一个类型为SmartDevicesmartDevice变量,该变量实例化一个接受"Android TV"参数和"Entertainment"参数的SmartTvDevice对象。
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
}
  1. smartDevice变量后一行,在smartDevice对象上调用turnOn()方法。
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
    smartDevice.turnOn()
}
  1. 运行代码。

输出如下所示

Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
  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()
}
  1. 运行代码。

输出如下所示

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()方法,你会注意到在SmartTvDeviceSmartLightDevice子类中调用这些方法时,deviceStatus变量的更新方式是相似的:代码重复了。当您在SmartDevice类中更新状态时,可以重用这段代码。

要从子类调用超类中的重写方法,需要使用super关键字。从超类调用方法类似于从类外部调用方法。不需要在对象和方法之间使用.运算符,而是需要使用super关键字,它通知Kotlin编译器在超类而不是子类上调用该方法。

从超类调用方法的语法以super关键字开头,后跟.运算符、函数名和一组括号。如果适用,括号中包含参数。您可以在此图中看到语法。

18cc94fefe9851e0.png

重用SmartDevice超类中的代码。

  1. turnOn()turnOff()方法中删除println()语句,并将SmartTvDeviceSmartLightDevice子类中重复的代码移动到SmartDevice超类。
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

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

    open fun turnOff() {
        deviceStatus = "off"
    }
}
  1. 使用super关键字在SmartTvDeviceSmartLightDevice子类中从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属性。

  1. SmartDevice超类中,在deviceStatus属性后一行,使用openval关键字定义一个设置为"unknown"字符串的deviceType属性。
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    open val deviceType = "unknown"
    ...
}
  1. SmartTvDevice类中,使用overrideval关键字定义一个设置为"Smart TV"字符串的deviceType属性。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    ...
}
  1. SmartLightDevice类中,使用overrideval关键字定义一个设置为"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` 属性和方法。

定义类时,它对任何导入它的包都是公开可见的,这意味着除非您指定可见性修饰符,否则它默认是公开的。类似地,当您在类中定义或声明属性和方法时,默认情况下,可以通过类对象从类外部访问它们。为代码定义正确的可见性至关重要,主要目的是隐藏其他类不需要访问的属性和方法。

例如,考虑一下汽车如何供驾驶员使用。汽车的组成部分以及汽车内部的工作原理的细节默认情况下是隐藏的。汽车的目的是尽可能易于操作。您不希望汽车的操作像商用飞机那样复杂,就像您不希望其他开发人员或您未来的自己对类的哪些属性和方法应该使用感到困惑一样。

可见性修饰符可帮助您向项目中的其他类公开代码的相关部分,并确保不会无意中使用实现,从而使代码易于理解且不易出错。

可见性修饰符应放在声明语法之前,在声明类、方法或属性时,如下图所示

dcc4f6693bf719a9.png

指定属性的可见性修饰符

指定属性可见性修饰符的语法以privateprotectedinternal修饰符开头,后跟定义属性的语法。您可以在此图中看到语法

47807a890d237744.png

例如,您可以在此代码片段中看到如何使deviceStatus属性变为私有。

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

    ...

    private var deviceStatus = "online"

    ...
}

您还可以将可见性修饰符设置为setter函数。修饰符放在set关键字之前。您可以在此图中看到语法

cea29a49b7b26786.png

对于SmartDevice类,deviceStatus属性的值应该可以通过类对象在类外部读取。但是,只有该类及其子类才能更新或写入该值。要实现此要求,需要在deviceStatus属性的set()函数上使用protected修饰符。

deviceStatus属性的set()函数上使用protected修饰符

  1. 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

    ...
}
  1. SmartHome类中,定义一个值为0deviceTurnOnCount属性,并使用私有setter函数。
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    var deviceTurnOnCount = 0
        private set

    ...
}
  1. 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()
    }

    ...

}

方法的可见性修饰符

指定方法可见性修饰符的语法以privateprotectedinternal修饰符开头,后跟定义方法的语法。您可以在此图中看到语法

e0a60ddc26b841de.png

例如,您可以在此代码片段中看到如何在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关键字和括号。

您可以在此图中看到语法。

6832575eba67f059.png

例如,您可以在此代码片段中看到如何在SmartDevice构造函数中添加protected修饰符。

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

    ...

}

类的可见性修饰符

指定类可见性修饰符的语法以privateprotectedinternal修饰符开头,后跟定义类的语法。您可以在此图中看到语法

3ab4aa1c94a24a69.png

例如,您可以在此代码片段中看到如何在SmartDevice类中指定internal修饰符。

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

    ...

}

理想情况下,应尽量严格控制属性和方法的可见性,因此应尽可能使用private修饰符声明它们。如果无法将其设为私有,请使用protected修饰符。如果无法将其设为受保护的,请使用internal修饰符。如果无法将其设为内部的,请使用public修饰符。

指定合适的可见性修饰符

此表可帮助您根据类或构造函数的属性或方法应可访问的位置确定合适的可见性修饰符

修饰符

在同一类中可访问

在子类中可访问

在同一模块中可访问

在模块外部可访问

private

protected

internal

public

SmartTvDevice子类中,不应允许从类外部控制speakerVolumechannelNumber属性。这些属性只能通过increaseSpeakerVolume()nextChannel()方法来控制。

类似地,在SmartLightDevice子类中,brightnessLevel属性只能通过increaseLightBrightness()方法来控制。

SmartTvDeviceSmartLightDevice子类添加合适的可见性修饰符

  1. SmartTvDevice类中,向speakerVolumechannelNumber属性添加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
            }
        }

    ...
}
  1. 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标识符来引用它。

查看目前的代码,您可以看到在SmartTvDeviceSmartLightDevice类中,用于检查speakerVolumechannelNumberbrightnessLevel属性的值是否在范围内的重复代码。您可以使用委托在setter函数中重用范围检查代码。与其使用字段以及getter和setter函数来管理值,不如由委托来管理。

创建属性委托的语法以变量声明开头,后跟by关键字,以及处理属性的getter和setter函数的委托对象。您可以在此图中看到语法

928547ad52768115.png

在实现可以委托实现的类之前,您需要熟悉接口。接口是一个契约,实现它的类需要遵守该契约。它关注的是做什么而不是如何做。简而言之,接口可以帮助您实现抽象

例如,在建造房屋之前,您会告诉建筑师您想要什么。您想要一个卧室、儿童房、客厅、厨房和几个浴室。简而言之,您指定您想要什么,而建筑师则指定如何实现它。您可以在此图中看到创建接口的语法

bfe3fd1cd8c45b2a.png

您已经了解了如何扩展类和重写其功能。对于接口,类实现接口。该类为接口中声明的方法和属性提供实现细节。您将对ReadWriteProperty接口执行类似的操作以创建委托。您将在下一单元中学习有关接口的更多信息。

要为var类型创建委托类,您需要实现ReadWriteProperty接口。类似地,您需要为val类型实现ReadOnlyProperty接口。

创建var类型的委托

  1. main()函数之前,创建一个实现ReadWriteProperty<Any?, Int>接口的RangeRegulator
class RangeRegulator() : ReadWriteProperty<Any?, Int> {

}

fun main() {
    ...
}

不用担心尖括号或其中的内容。它们表示泛型类型,您将在下一单元中学习有关它们的信息。

  1. RangeRegulator类的主要构造函数中,添加一个initialValue参数、一个私有的minValue属性和一个私有的maxValue属性,它们都是Int类型
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

}
  1. 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函数。

  1. SmartDevice类之前的行中,导入ReadWritePropertyKProperty接口。
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

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

...

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

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

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

...
  1. 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) {
    }
}

此属性充当变量的_支持字段_。

  1. 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) {
    }
}
  1. 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
        }
    }
}
  1. SmartTvDevice类中,使用委托类定义speakerVolumechannelNumber属性。
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

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

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

    ...

}
  1. 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类中调用SmartTvDeviceSmartLightDevice类的相应方法。
  • main()函数中,调用这些新增的方法来测试它们。

12. 结论

恭喜!您学习了如何定义类和实例化对象。您还学习了如何在类之间创建关系以及创建属性委托。

总结

  • 面向对象编程的四个主要原则是:封装、抽象、继承和多态。
  • 类是用class关键字定义的,包含属性和方法。
  • 属性类似于变量,不同的是属性可以具有自定义的getter和setter。
  • 构造函数指定如何实例化类的对象。
  • 定义主构造函数时,可以省略constructor关键字。
  • 继承使代码重用更容易。
  • IS-A关系指的是继承。
  • HAS-A关系指的是组合。
  • 可见性修饰符在实现封装中起着重要作用。
  • Kotlin 提供四种可见性修饰符:publicprivateprotectedinternal修饰符。
  • 属性委托允许您在多个类中重用getter和setter代码。

了解更多