目录
面向对象思想
面向对象不仅是一种编程思想,更是解决问题和处理数据的方式,面向对象是一种对现实世界的理解和抽象的方法。
以面向对象思想为基础发展而来的技术主要有:
- OOA(Object Oriented Analysis,面向对象的分析)
- OOD(Object Oriented Design,面向对象的设计)
- OOP(Object Oriented Programming,面向对象的编程实现)
面向对象编程
面向对象编程,又称为面向对象的程序设计,简称 OOP,本质是一种程序设计的范型,也是计算机编程技术发展到一定阶段后的产物。
在客观世界中,对象是人们要进行研究的任何 “事” 或 “物”,例如:某些规则、计划或事件等等概念上的 “事”,以及某些动物、汽车或书籍等等实际上的 “物”。
在数字世界中,对象(Object)实现了数据和操作的结合,使数据和操作封装到对象的统一个体中。主要具有两个特征:
- 对象状态:通过某些 “数据” 来描述一个对像的状态。
- 对象行为:通过某些 “操作” 来改变一个对象的状态。
同样在数字世界中,类(Class)则是对具有相同状态(数据)和行为(操作)的对象的抽象。
- 类属性:是对象状态的抽象,用数据结构来描述。
- 类方法:是对象行为的抽象,用实现特定操作的方法来描述。
因此,在很多 OOP 编程语言中,对象的抽象就是类,类的实例(具体)化就是对象。抽象指将具有一致的 “状态” 和 “行为” 的某种对象,抽象为具有统一 “属性” 和 “方法” 的类。类的抽象划分往往是主观的,应该要反映与程序应用相关的重要性质,而忽略其他一些无关的内容。
简而言之,OOP 是以 “数据” 和 “操作” 为中心的,通过数据结构设计来组织数据,并提供对此类数据所允许的操作。计算机程序的本质就是 “数据结构 + 算法”。
面向对象编程的特性
封装
封装,顾名思义是 “封闭性的包装”,其目的是将 “数据” 以及 “行为” 进行适度的隐藏,通过 “接口” 的方式进行暴露,使的代码逻辑模块化(松耦合、高内聚)。具体地说,在类的定义和实现时,需要为数据提供相应的接口,以免 Client 对数据进行不规范的操作。
被封装好的每个类都是独立的,由类所实例化得到的任何对象也都具有唯一性。OOP 中的对象都应该能够接受数据、处理数据、传达数据给其他的对象。并且对象之间只能够通过 “消息” 来进行通信。在对象的操作中,当一个消息发送给某个接收对象时,消息应该包含了该接收对象去执行某种操作的数据。
可见,封装能够实现:类的属性和方法能够只让可信的类或者对象进行操作,对不可信的操作进行信息隐藏。为类和对象中的属性、方法提供了安全的保障。具体的说,在一个对象内部,某些数据或行为可以是 Private(私有的),它们不能被外界访问。
换言之,一个类就是一个封装了数据以及操作这些数据的方法的逻辑实体。OOP 通过类和对象的封装技术以及消息机制,使得编程可以像搭积木的一样快速开发出一个全新的系统。
继承
继承使得不同的类之间拥有了一定的结构与关系。
继承是一种子类自动继承父类属性和方法的机制,在定义和实现一个子类的时候,可以在一个已经定义好的夫类的基础之上进行,加入若干新的内容,这些都不会影响到父类。所以,继承所带来的最明显的好处在于代码的可重用性。
继承的实现方式大致上有 2 大类、4 小类:
- Inheritance(继承),又名 “单重继承”:子类只继承一个父类的属性和方法。
- 实现继承:子类的属性和方法完全继承于父类。
- 可视继承:子类继承了父类的外观和方法。
- Composition(组合),又名 “多重继承”:子类继承了多个父类的属性和方法。
- 接口继承:子类的属性命和方法名继承于父类,但是具体的属性值和方法实现由子类重写。
- 纯虚类
注意,在实现多重继承时,如果继承的多个父类中均有名称相同的属性或方法时,要注意继承的顺序。
多态
多态指对于相同的类方法或函数,可作用于多种类型的对象上,并获得不同的结果,即:在不同的对象中可以拥有相同名称的属性或方法,但是属性值或方法实现却各不相同。也就是说,多种类型的对象都可以使用同一个类方法或函数,并且可以得到相应且不同的结果。这种现象称为多态性。
多态使得不同的对象能够通过它们共同的属性和方法来进行操作,而不需考虑它们具体的类型是否相同,继而使得动态绑定(运行时)成为了可能,允许重载及运行时类型确定。
多态常用于 “接口重用” 场景,保证了在类发生继承时,仍然能够正确的调用这些相关类的实例化对象的属性和方法。
面向对象编程的优势
- 以人更容易理解的方式对复杂的系统进行分析和设计。
- 降低开发难度:模块之间、组件之间、系统之间通过 API 来进行交互,以此屏蔽底层复杂性。
- 提高代码重用性:继承和多态提供了极高的代码重用性。
- 提到软件架构的可扩展性:松耦合的模块化设计,对单一模块的修改并不会影响到整合系统。
Python 的类属性与类方法
现在 Python 主流的类定义是 “新式类定义”,要求每个类都必须至少继承一个父类,如果没有特定的父类时,则继承基类 object。
Python 使用 cls 关键字来作为类的句柄。
- 类方法:将 cls 关键字作为第一个形式参数。当类通过句点标识符来调用一个类方法时,Python 解析器会隐式的将类的引用作为实参传递给 cls 形参。在类方法中可以直接使用 cls 关键字来调用类属性或方法。
使用类方法修饰符来进行声明:
class NewClass(object):
@classmethod
foo(cls):
pass
- 类属性:在类体中直接定义,是一个与具体某个对象无关的属性。所以类属性只能通过类方法或类调用来进行访问或更新。相对于对象属性而言,类属性更加 “静态”,当类属性被定义在了类方法中时,该类属性并不会因为类方法调用完毕而被回收,直到这个类被回收为止。
__name__
:类的名字__module__
:类的模块名称__dict__
:类的属性和方法映射字典__class__
:类实例化对象的类名称
特殊的类属性:
Python 类的实例化
类的构造器 __new__()
是一个类方法,用于实例化(生成)一个对象,再将该对象传入 __init__()
实现初始化操作。
实际上 __new__()
通常不需要进行重载,一般只会在派生 Python build-in 的不可变类型(e.g. string、float etc…)的子类时候(从标准类中进行派生)才需要。例如:通过重载 float 的构造器 __new__()
来定制一个新的不可变类型 RoundFloat。
class RoundFloat(float):
def __new__(cls, val):
return float.__new__(cls, round(val, 2))
- 1
- 2
- 3
而 __init__()
则是一个对象方法,用于在构造器实例化了对象之后,执行一些特定的初始化工作。它在实例化一个新的对象时被自动调用,所以除了初始化对象属性之外,还常被用于运行一些初步的诊断代码。
需要注意的是,在继承中,基类的 __init__()
不会被自动调用,需要在子类的 __init__()
中进行显式调用。
Python 的对象属性与对象方法
Python 使用 self 关键字来作为对象的句柄,类似于 Java 的 this 关键字,用于支撑封装特性,保证同一个类的多个对象之间的数据隔离安全。对象属性和对象方法都会绑定到 self,表示绑定到一个具体的对象。
- 对象属性:通过 self 调用的属性。
- 对象方法:第一个形参为 self 的方法。
使用 self 来标识对象方法时,self 会作为第一个形参。当对象通过句点标识符来调用一个对象方法时,Python 解析器会隐式的将对象的引用作为实参传递给 self 形参。
其中,对象的 __dict__
属性是一个包含了所有对象属性和方法的字典,方 Python 解析器访问一个对象属性或方法时,就会在这个字典中搜索。如果在该类的字典中没有搜索到,那么就会按照位置先后的顺序逐一到父类的字典中搜索。如此的,就能够将不同类之间的命名隔离开来,在子类中对 __dict__
字典的修改并不会影响到父类。
Python 类的继承
子类会继承父类所有公开的(public)属性和方法,或者在子类中对这些属性和方法进行重载。反过来说,子类不能够继承父类中私有的(Private)属性和方法(以 __
为前缀的命名)。
子类也同样应该拥有自己的类初始化器,这意味着在实例化子类的对象时,需要同时传递子类和父类的类初始化器所要求的实参列表,且需要在子类初始化器中显式的调用父类初始化器来完成实参的传递。
另外,由于 Python 是支持多重继承的,所以 Python 提供了 super()
build-in 函数来实自动且准确的找出子类的父类来进行继承。也因此,在进行多重继承时,要严格注意父类列表的顺序位置。
在一些复杂的继承关系中,Python 需要通过 C3 或 MRO 算法来将类图转换成序列。MRO 算法,可实现以下几个理想特性:
- 每个祖先类只出现一次。
- 单调性:子类总是出现在其祖先类之前。
- 一致的本地优先顺序:同一类的直接父类应按照类定义中列出的顺序显示。
- 一致的扩展优先顺序:如果 A 类的子类总是出现在 B 类的子类之前,则 A 应出现在 B 类之前。
单重继承
class Car():
"""这是一个汽车类"""
def __init__(self, brand, color):
self.brand = brand
self.color = color
def run(self, s):
print("当前行驶速度:%s KM/S" % s)
class OilCar(Car):
"""这是燃油汽车类"""
def __init__(self, car_power, brand, color):
super().__init__(brand, color)
self.car_power = car_power
def Info(self):
print("子类中调用父类方法:", self.run(444))
class Oil(OilCar):
def __init__(self,car_power, brand, color, oilInfo):
super().__init__(car_power, brand, color)
self.oilInfo = oilInfo
def oil_info(self):
print(self.brand)
print(self.Info())
oil = Oil("燃油","宝马","蓝色","汽油")
oil.oil_info()
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
多重继承
多重继承一般会有两种继承方式:
- 链式继承:A 继承 B,B 继承 C。
- 菱形继承:A继承 B 和 C(B、C 无关系)。
class A(object):
def __init__(self, aval, **kwargs):
print("A: rcd value: ", aval)
self.aval = aval
super().__init__(**kwargs)
class B(object):
def __init__(self, b1val ,b2val, **kwargs):
print("B: rcd 2 values: ",b2val)
self.b1val = b1val
self.b2val = b2val
super().__init__(**kwargs)
class C(A, B):
def __init__(self, a, b, c, **kwargs):
super().__init__(aval=a, b1val=b, b2val=c, **kwargs)
c = C(1, 2, 3)
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
在多重继承中需要避免重复继承一个类,可能会造成一些错误。例如:
class A(object):
def __init__(self, name):
self.name = name
def get(self):
return self.name
class B(A):
def get(self):
super(B, self).get()
class C(A,B):
def get(self):
super(C, self).get()
c = C("Name")
# TypeError: Cannot create a consistent method resolution
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18