面向对象编程 (OOP)

到目前为止,你所进行的编程是过程式编程。然而,今天的许多程序都是面向对象的。了解这两种编程方式,并知道它们之间的区别是非常重要的。许多计算机科学中的重要语言,如 C++ 和 Java,通常使用面向对象编程(OOP)方法。

对于初学者和非程序员来说,OOP 的概念常常让人感到困惑和复杂。这是正常的。如果你感到困惑或无法理解,不要气馁。如果这一章没有帮助你,有很多其他的资源可以帮助你克服可能遇到的问题。

本章将分为不同的课程。每节课将以不同的方式解释 OOP,确保 OOP 被尽可能全面地讲解,因为它非常重要。在课程之前,有一个介绍部分,解释了 OOP 的关键概念、术语和其他重要的领域,理解这些是理解每节课的基础。

简介

将过程视为一个函数。函数有一个特定的目的。这个目的可能是获取输入、执行数学计算、显示数据,或处理文件中的数据。通常,过程使用与代码分离的数据进行操作。这些数据常常在过程之间传递。当程序变得更大、更复杂时,这可能会引发问题。例如,你设计了一个程序,使用变量存储关于产品的信息。当客户请求产品信息时,这些变量会被传递给不同的函数以完成不同的任务。随着更多的数据存储在这些产品中,你决定将信息存储在列表或字典中。为了让程序继续正常运行,你现在必须编辑每个接收变量的函数,使它们接受并操作列表或字典。想象一下,如果程序有数百兆字节大,且包含数百个文件,这将需要多长时间来编辑!这将让你崩溃!更不用说,代码中几乎肯定会出错,仅仅因为大量的工作和输入错误的可能性。这显然不是最优的。过程式编程以过程或函数为中心。但 OOP 以创建对象为中心。记得过程式编程将数据和代码分开吗?记得那个庞大的程序需要花费多长时间来编辑吗?那么,想象一下,将这些文件和数据“合并”成一个“实体”的对象。在技术上,对象是一个包含数据和过程(代码、函数等)的实体。

对象中的数据称为数据属性。

对象中的函数或过程称为方法。

可以将数据属性看作是变量。

可以将方法看作是函数或过程。

让我们看一个简单的日常例子。假设你卧室里的灯和开关。数据属性如下所示:

  • light_on(True 或 False)
  • switch_position(Up 或 Down)
  • electricity_flow(True 或 False)

方法如下所示:

  • move_switch
  • change_electricity_flow

数据属性可能是不可见的。例如,你不能直接看到电流流向灯泡。你知道灯亮着是因为电流在流动,但你无法直接看到电流流动。然而,你可以看到开关的位置(switch_position),也可以看到灯是否亮着(light_on)。一些方法是私有的,意味着你不能直接更改它们。例如,除非你切断灯具中的电线(为了本示例假设你不知道电线的存在),否则你无法直接改变电流的流动。你也不能直接更改灯是否亮着(而且不,不能拧下灯泡!)。然而,你可以通过使用对象中的方法间接地改变这些属性。如果你没有缴纳电费,change_electricity_flow 方法将会把 electricity_flow 属性的值更改为 False。如果你翻动开关,move_switch 方法将会改变 light_on 属性的值。

到这里,你可能会想,“这和 Python 有什么关系?”或者,“我明白了,但是如何在代码中实现对象呢?”好吧,我们差不多要到达那个点了!在我们深入代码之前,还有一个概念需要解释。

在 Python 中,对象的数据属性和方法是由类来定义的。可以把类看作是对象的蓝图。例如,你的家——你居住的物体——你也可以称之为你的住所、平房、单元房等等——它是根据一套蓝图建造的;这些蓝图就相当于设计你家、住所或平房的类。

再次强调,类告诉我们如何创建一个对象。在技术术语中,这一点非常重要:类定义了对象内部的数据属性和方法。

为了创建一个类,我们需要编写一个类定义。类定义是一组语句,定义了对象的数据属性和方法。

第一课

以下是一个过程式编程示例,执行简单的数学运算,针对用户输入的单个数字。

# 程序由 Mitchell Aikens 编写
# 无版权声明
# 2012

# 程序1
def main():
    try:
        # 获取要操作的数字
        num = float(input("请输入一个数字进行操作。\n"))
        # 通过程序2操作后的结果
        addednum = addfive(num)
        # 通过程序3操作后的结果
        multipliednum = multiply(addednum)
        # 将值传递给程序4
        display(multipliednum)
    # 处理来自用户输入非数字的异常
    except ValueError:
        print("请输入一个有效的数字。\n")
        # 重置 num 的值,清除非数字数据。
        num = 0
        # 重新调用 main。
        main()

# 程序2
def addfive(num):
    return num + 5

# 程序3
def multiply(addednum):
    return addednum * 2.452

# 程序4
def display(multi):
    # 显示最终值
    print("最终值是 ", multi)

# 调用程序1
main()

如果我们输入数字 "5",输出将如下所示:

请输入一个数字进行操作。
5
最终值是 24.52

如果我们输入 "g" 然后修正输入并输入 "8",输出将如下所示:

请输入一个数字进行操作。
g
请输入一个有效的数字。

请输入一个数字进行操作。
8
最终值是 31.875999999999998

下面是一个类和使用该类的程序。这个面向对象编程程序与上述过程式编程程序执行相同的功能。在我们深入类和程序之前,先介绍一些重要的 OOP 编码概念。要创建一个类,我们使用 class 关键字。关键字后面是你希望为类命名的名称。按照惯例,类名通常使用驼峰命名法(CapWords)。例如,如果我想创建一个名为 dirtysocks 的类,代码如下:

class DirtySocks:

首先展示类的定义,然后是使用该类的程序。

# 文件名:oopexample.py
# Mitchell Aikens
# 无版权声明
# 2012
# OOP 演示 - 类

class NumChange:

    def __init__(self):
        self.__number = 0

    def addfive(self, num):
        self.__number = num
        return self.__number + 5

    def multiply(self, added):
        self.__added = added
        return self.__added * 2.452

这个类定义了一个 NumChange 类,它具有两个方法:addfivemultiplyaddfive 方法接受一个数字并将其加 5,multiply 方法接受一个已加 5 的数字,并将其乘以 2.452。

使用上述类的程序如下:

# 文件名:oopexampleprog.py
# Mitchell Aikens
# 无版权声明
# 2012
# OOP 演示 - 程序

import oopexample

maths = oopexample.NumChange()

def main():

    num = float(input("请输入一个数字。\n"))

    added = maths.addfive(num)

    multip = maths.multiply(added)

    print("操作后的值是 ", multip)

main()

在查看这个程序后,你可能有些困惑。这没关系。我们从解析类开始。类的名称是 NumChange,它包含三个方法:

  • __init__
  • addfive
  • multiply

这三个方法的代码结构是相似的:

def __init__(self):
def addfive(self, num):
def multiply(self, added):

注意每个方法都有一个名为 self 的参数。这个参数必须出现在类的每个方法中。虽然这个参数不一定必须叫做 self,但这是标准做法,所以你应该遵循这个习惯。这个参数之所以必需,是因为当方法执行时,它必须知道在哪个对象上操作其属性。即使只有一个对象,我们仍然需要确保解释器知道我们要使用该类中的数据属性。因此,我们通常使用 self 参数。

让我们来看第一个方法:

def __init__(self):

在 Python 中,大多数类都有一个 __init__ 方法,当类的实例(对象)在内存中创建时,它会自动执行。(当我们引用一个类时,类的一个实例 [或对象] 被创建。)这个方法通常被称为初始化方法。当这个方法执行时,self 参数会自动被分配给对象。这个方法之所以叫做初始化方法,是因为它 "初始化" 数据属性。在 __init__ 方法下,我们将 number 属性的值初始化为 0。我们通过点符号来引用对象的属性:

def __init__(self):
    self.__number = 0

self.__number = 0 这一行意味着:"对象的属性 number 的值为 0"

接下来是第二个方法:

def addfive(self, num):
    self.__number = num
    return self.__number + 5

这个方法叫做 addfive。它接受一个名为 num 的参数,这个参数来自使用该类的程序。方法将这个参数的值赋给对象内的 number 属性,然后返回 number 的值加上 5,返回的结果会传回调用该方法的语句。

接下来是第三个方法:

def multiply(self, added):
    self.__added = added
    return self.__added * 2.453

这个方法叫做 multiply。它接受一个名为 added 的参数。方法将该参数的值赋给 added 属性,并返回 added 属性的值乘以 2.452,返回结果传回调用该方法的语句。

注意每个方法的名称都以两个下划线开始吗?我们来解释一下。之前我们提到过,对象通过方法操作其内部的数据属性。理想情况下,这些数据属性应该只能通过对象内的方法来操作。外部代码可以操作数据属性的做法是可以避免的。为了 "隐藏" 属性,只允许对象内的方法来操作它们,我们在属性名之前使用了两个下划线(正如我们展示的那样)。如果省略这两个下划线,外部代码也有可能操作这些属性。

接下来我们看看使用刚才分析的类的程序。

注意代码中的第一行非注释代码:

import oopexample

这一行代码导入了我们保存在单独文件(模块)中的类。类不一定要放在单独的文件中,但通常都是这样,所以现在习惯性地进行模块导入是一个好习惯。

接下来这一行:

maths = oopexample.NumChange()

这一行创建了 NumChange 类的实例,该类存储在名为 oopexample 的模块中,并将该实例存储在变量 maths 中。语法是:模块名.类名()。接下来我们定义了 main 函数,并获取用户输入的数字。

下一行:

added = maths.addfive(num)

这行代码将 num 变量的值传递给 addfive 方法,它是我们存储在 maths 变量中的类的一部分,然后将返回的值存储在 added 变量中。

下一行:

multip = maths.multiply(added)

这行代码将 added 变量的值传递给 multiply 方法,它也是我们存储在 maths 变量中的类的一部分,并将返回的值存储在 multip 变量中。

接下来这一行:

print("操作后的值是 ", multip)

这行代码打印 "操作后的值是 <multip 的值>"。最后一行调用 main 函数,执行上述步骤。

Last modified: Saturday, 11 January 2025, 11:34 AM