Skip to content

Latest commit

 

History

History
127 lines (84 loc) · 7.78 KB

173.md

File metadata and controls

127 lines (84 loc) · 7.78 KB

面向对象编程(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,例如,您可以在其中使用privateprotectedpublic来限制对字段和方法的访问。 但是在 Python 中没有这样的限制级别。 您可以访问类的每个字段和函数。

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

数据抽象

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

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

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

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

继承

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

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

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

继承是 is-a 关系,意思是汽车是车辆。 对象组成是的关系,意味着汽车具有车轮(或至少汽车具有车轮)。

多态

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

>>> 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 元组(一对)来存储点。 点列表代表一个形状(多边形)。 因此,您首先要定义一个列表,其中包含一些表示点的对:

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

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

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 定义和使用对象(类)的方式,并且我必须事先告诉您还有许多您无法想象的方式。