Python 设计模式——理解面向对象编程

面向对象编程

面向对象的编程范式引入了对象的概念,对象具有属性和用来处理属性的成员函数。
比如对象 Car 拥有多种属性,如 fuel_level(油位)、is_sedan(是否为轿车)、speed(速度)、steering_wheel(方向盘)和 coordinates(坐标)等。
同时还拥有一些方法,比如 accelerate() 方法用来提升速度,takeleft() 方法让车左转等。

在 Python 中,一切皆对象。每个类的实例或变量都有它自己的内存地址或身份。对象就是类的实例,应用开发就是通过对象交互来实现目的的过程。

对象

  • 表示所开发的应用程序内的实体
  • 实体之间可以通过交互来解决现实世界的问题
  • 如 Person 是实体,Car 也是实体。Person 可以驾驶 Car,从一个地方开到另一个地方

  • 类可以定义对象的属性和行为。属性是数据成员,行为由成员函数表示
  • 类包含了构造函数,为对象提供初始状态
  • 类就像模板,非常易于重复使用

方法

  • 表示对象的行为
  • 可以对属性进行处理,实现所需的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def get_person(self,):
return f"<Person ({self.name}, {self.age}>)"


p = Person("John", 32)
print(f"Type of Object: {type(p)}, Memory Address: {id(p)}")
print(p.get_person())
# => Type of Object: <class '__main__.Person'>, Memory Address: 140586467650144
# => <Person (John, 32>)

主要概念

封装

  • 对象的行为对于外部是不可见的,或者说对象的状态信息是私密的
  • 客户端不能通过直接操作来改变对象的内部状态。需要通过发送消息来请求对象改变其内部状态,对象则根据请求通过特定的成员函数完成内部状态的修改
  • 在 Python 中,封装的概念不是隐式的,它没有提供封装所需的诸如 public、private 和 protected 等关键字

多态

  • 多态有两种类型。对象根据输入的参数提供方法的不同实现;不同类型的对象可以使用相同的接口
  • 对于 Python 而言,多态是内置功能。如操作符 + 既可以应用于两个整数进行加法运算,也可以应用于字符串用来连接它们
1
2
3
4
5
>>> a = "John"
>>> b = (1, 2, 3)
>>> c = [3, 4, 6, 8, 9]
>>> print(a[1], b[0], c[2])
o 1 6

继承

  • 表示一个类可以继承父类的(大部分)功能
  • 可以重用基类中定义的功能并允许子类独立地进行扩展
  • 可以利用不同类对象之间的关系建立层次结构。Python 支持多重继承(继承多个基类)

设计原则

开放/封闭原则
类或对象及其方法对于扩展来说应该是开放的,对于修改来说应该是封闭的。
即在开发软件的时候,一定确保以通用的方式来编写类或模块,以便需要扩展类或对象行为的时候不必修改类本身。

优点:

  • 现有的类不会被修改,退化的可能性较小
  • 有助于保持以前代码的向后兼容性

控制反转原则
高层级的模块不应该依赖于低层级的模块,应该都依赖于抽象。细节依赖于抽象,而不是抽象依赖于细节。
该原则建议任何两个模块都不应以紧密方式相互依赖。基本模块和从属模块应当在它们之间提供一个抽象层来耦合。类的细节应描绘抽象。

优点:

  • 削弱了模块间的紧耦合,消除了系统中的复杂性和刚性
  • 在依赖模块之间有一个明确的抽象层,便于通过更好的方式处理模块之间的依赖关系

接口隔离原则
客户端不应依赖于它们不需要使用的接口。

优点:

  • 强制开发人员编写“瘦身型”接口,并使方法与接口紧密相关
  • 防止向接口中随意添加方法

单一职责原则
类的职责单一,引起类变化的原因单一。
当我们在开发类时,它应该为特定的功能服务。若一个类实现了两个功能,则最好将它们分开。

优点:

  • 每当一个功能发生变化时,除了特定的类需要改变外,其他类无需变动
  • 若一个类有多种功能,那么依赖它的类必定会由于多种原因经历多次修改。这是应该避免的

替换原则
派生类必须能够完全取代基类。
当应用程序开发人员编写派生类时,该原则的含义就是他们应该扩展基类。此外,派生类应该尽可能对基类封闭,以致于派生类本身可以替换基类而无需修改任何代码。