# 面向对象编程（OOP）

> 原文： [https://javabeginnerstutorial.com/python-tutorial/object-oriented-programming-oop/](https://javabeginnerstutorial.com/python-tutorial/object-oriented-programming-oop/)

现在，我们进行了面向对象的编程。 曾经有过这样一种炒作：每种语言都是围绕对象设计的，而 Python 开发人员 Guido van Rossum 认为“为什么不呢？” 并添加了类以支持面向对象的开发。 一些 python 传福音者认为这是一个错误的决定，有些认为这是一个好方法……

面向对象是一个大话题。 可以写一本关于它的书，但我会坚持在文章的狭小范围内。 或最多有两篇有关 OO 的常规文章和 Python 中的 OO 文章。

## 一般的面向对象

面向对象技术大约在 60 年代后期，但直到 90 年代初才在开发人员中获得了发展空间。 我们将学习以下四个主要原则：

*   封装形式
*   数据抽象
*   继承
*   多态

如果我想模拟现实生活，我会说 OO 就像一家餐馆。 您可以有两种类型：一种可以在柜台上找到食物，也可以用食物自助服务。 另一个是您进餐的地方，它是由专业服务准备并带给您的。

带有自助服务的第一个版本是命令式语言使用的东西（例如 C 或简单的 Python 脚本），在这里每个人都可以访问所有内容，并且他们可以使用他们想要的东西。 在这种情况下，有时会将碗碟留在桌子上，因为带回碗碟是客户的工作。

第二个版本是 OO。 在那里您可以封装功能，并且只能访问那些公开可用的部分。 如果您已经使用 Java 或 C++ 开发过，您可能会知道公开，受保护和私有访问的概念。 在这种情况下，通过员工来获取食物并带回餐具。 他们知道从何处，何处放东西可以得到什么，而最终用户并不需要了解一切。

如果我们回头看面向对象，那么我们可以说一类是一个对象的定义，一个对象是指定类的实例。

一个类定义了未来对象具有的属性和功能，以及该语言是否使用访问限制，您可以在类定义中告诉公众可以访问哪些部分，该类的扩展还是仅内部使用。

现在是时候深入研究 OOP 的四大原则了。

### 封装形式

封装是指将数据和功能打包到单个组件中。 但是，在我们的情况下，进入一类，其他编程语言支持其他替代方法。 该类的功能将根据存储在该类的字段中的数据进行操作。

在某些编程语言中，封装用于隐藏信息，或更准确地说：限制对数据和功能的访问。 这些语言包括 C++ 和 Java，例如，您可以在其中使用`private`，`protected`和`public`来限制对字段和方法的访问。 但是在 Python 中没有这样的限制级别。 您可以访问类的每个字段和功能。

但是，有一个约定没有写下来，但是每个 Python 开发人员都知道并且应该知道：名称以双下划线（`__`）开头的类（字段和函数）的成员应视为私有的，不应调用或访问。

### 数据抽象

数据抽象强制将类型的抽象属性与实现细节之间的清晰区分。 抽象属性是那些使用此数据类型对客户端可见的属性（在我们的情况下为类，在其他编程语言中为接口定义），并且实现对客户端隐藏并为私有。

而且由于实现是私有的，因此可以随时间更改（例如，使代码更快），并且客户端不会注意到此更改，因为抽象保持不变。

好吧，在 Python 中，没有什么比其他 OO 语言的接口更好。 您只有一个班级，这个班级有它的字段和功能。 当然，您可以具有一个“公开”函数作为外部代码的接口，以及一个或多个实现该“公开”函数的逻辑的“私有”函数。 但这不是真正的抽象。

但是，Python 知道用于只读字段的解决方案，这些字段是通过所谓的“获取器”方法即时计算的。 当然，对于读写字段，Python 也使我们也可以使用“设置器”方法。 我们将在后面看到这两个示例。

### 继承

继承是 OOP 的一项关键功能，其中一个类基于另一类的模板/实现（从该类继承）。 这是一种代码重用的基本方法，您可以将子类之间的通用功能和信息封装到一个基类中。

继承模型有不同类型，但是最常见的两种是*单继承*和*多继承*。 Python 使用**多重继承**，这意味着一个类可以根据需要扩展任意多个类。

继承通常与*对象组成*混淆。 有时，新的开发人员会尝试解决继承的所有问题，即使继承应该是对象组合。 对象组合意味着您拥有另一个类的实例的属性，但是您的类没有扩展它。 如果您不知道需要哪一个，请记住以下简单的解决方案：

继承是 **is-a** 关系，意思是*汽车是车辆*。 对象组成是**与**的关系，意味着*汽车具有车轮*（或至少汽车具有车轮）。

### 多态

在 OOP 中，多态是为多个类型提供单个接口。 在 Python 中，这意味着您希望将超类作为参数（例如，执行`isinstance()`检查）并在对象上调用该超类的通用方法。 现在，通过多态，将在所使用的子类中执行该方法的实际实现。

```java
>>> class Animal:
...     def sound(self):
...         raise NotImplementedError
...
>>> class Dog:
...     def sound(self):
...         print('woof')
...
>>> class Dog(Animal):
...     def sound(self):
...         print('woof')
...
>>> class Cat(Animal):
...     def sound(self):
...         print('meow')
...
>>> def animal_sound(animal):
...     if isinstance(animal, Animal):
...         animal.sound()
...     else:
...         print("Not an animal, do not know how to make it sound")
...
>>> cat = Cat()
>>> dog = Dog()
>>> animal_sound(dog)
woof
>>> animal_sound(cat)
meow
```

如您在上面的示例中所看到的，`animal_sound`函数验证该参数是`Animal`，然后调用该特定动物的`sound`方法。

## 什么时候使用 OO？

自然，OO 不是万能的油。 因此，在开发时应考虑使用 OOP。 在本节中，我将更深入地探讨何时应用本章的原理和技术。

确定何时使用面向对象的编程并不容易。 我们必须记住，对象具有数据**和**行为，这使事情变得复杂。 这就是为什么许多 Python 开发人员使用简单的数据结构（列表，集合，字典）和简单的函数的原因，除非确实需要额外的层抽象（我也是）。

现在，如果我们看到使用相同数据集调用函数，则可以考虑将数据封装到一个类中，然后将这些函数添加为类函数以表示行为。

一个简单的例子就是几何图形之外的东西。 在那里，您使用一个 2 元组（一对）来存储点。 点列表代表一个形状（多边形）。 因此，您首先要定义一个列表，其中包含一些表示点的对：

```java
triangle = [(2,3), (5,7), (0,0)]
```

现在，如果您要计算该三角形的周长，可以编写一个如下所示的函数：

```java
import math

def perimeter(points):
   perimeter = 0
   points_extended = points + [points[0]]
   for i in range(len(points)):
       perimeter += math.sqrt((points_extended[i][0] - points_extended[i+1][0])**2 + (points_extended[i][1] - points_extended[i+1][1])**2)
   return perimeter
```

到达这一点之后，您可能会感觉到有一个对象封装了三角形的所有点（数据）和周长函数（行为）。 如果您想得更多，可以将三角形点的 x 和 y 坐标封装到另一个对象中，然后将两个点的距离计算添加到该对象中。

Python 中有一些类可用于此封装。

在下一篇文章中，我将深入探讨 Python 定义和使用对象（类）的方式，并且我必须事先告诉您还有许多您无法想象的方式。

