在 Kotlin 中使用类和对象

1. 开始之前

本 Codelab 教您如何在 Kotlin 中使用类和对象。

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

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

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

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

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

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

先决条件

  • 如何在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 编写,因此每个单词都以大写字母开头,单词之间没有空格。例如,在SmartDevice 中,每个单词的第一个字母都大写,单词之间没有空格。

类由三个主要部分组成

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

这不是您第一次使用类。在之前的 Codelab 中,您学习了数据类型,例如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()函数表示,这些函数启用打开和关闭行为。

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

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类的​​方法来打开和关闭设备。

在类中调用方法的方式类似于你在之前的代码实验室中从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值。如果不是,则属性的值保持不变。

你在本代码实验室的“实现类之间的关系”部分将此属性包含在一个类中,因此你现在不需要向代码中添加 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 中,所有类默认都是最终的,这意味着您不能扩展它们,因此您必须定义它们之间的关系。

定义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. 定义一个分配给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
            }
        }
}
  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子类体中,定义一个分配给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
            }
        }
}
  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关键字定义一个SmartLightDevice类型的smartLightDevice属性
// 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关键字定义一个SmartDevice类型的smartDevice变量,该变量实例化一个SmartTvDevice对象,该对象接收一个"Android TV"参数和一个"Entertainment"参数。
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变量以实例化一个SmartLightDevice类,该类接收一个"Google Light"参数和一个"Utility"参数,然后在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 属性中,将 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

    ...
}
  1. SmartHome 类中,定义一个 deviceTurnOnCount 属性,将其设置为值为 0,并使用私有 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() 函数之前,创建一个 RangeRegulator 类,该类实现 ReadWriteProperty<Any?, Int> 接口
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 属性之前,检查要赋值的 value 参数是否在 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 代码。

了解更多