Object Oriented Programming
OOP (Object Oriented Programming)
面向对象的三大基本特征:封装、继承、多态。
封装:把客观事物封装成抽象的类,而且类中特定的属性或方法可以只提供给特定的类或对象使用,从而做到隐蔽性和安全性。
继承:继承可以让新创建的类获得被继承的类的属性和方法。通过继承创建的新类成为“子类”或“派生类”,被继承的类成为“父类”、“基类”或“超类”。继承概念的实现:实现继承和接口继承。
- 实现继承,是指子类可以直接使用基类的属性和方法;
- 接口继承,是指基类仅提供了一个接口方法,子类必须先实现了才能够使用。
某些 OOP 语言中,一个子类可以继承多个基类 (多重继承)。
多态:指子类重新定义父类的虚方法 (virtual, abstract)。当子类重新定义了父类的虚方法后,父类根据赋给它的不同子类,动态调用属于子类的该方法。
面向对象的五大基本原则:单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、接口隔离原则。
- 单一职责原则 (SRP, Single Responsibility Principle):就一个类而言,应该仅有一个引起它变化的原因。简言之,就是一个类的功能要单一。
- 开放封闭原则 (OCP, Open-Close Principle):对扩展开放,对修改封闭。
- 里氏替换原则 (LSP, the Liskov Substitution Principle):子类应当可以替换父类出现在父类能够出现的任何地方。
- 依赖倒置原则 (DIP, the Dependency Inversion Principle):传统结构化编程中,最上层的模块通常要依赖下层模块来实现,即高层依赖低层。DIP 原则就是要让高层模块不依赖低层模块,二者都应该依赖抽象。抽象不应该依赖具体 (细节),而具体 (细节) 应该依赖抽象。模块间的依赖通过抽象发生,实现类之间不产生直接的依赖关系,而是通过接口或抽象类产生。接口或抽象类不依赖于实现类;实现类依赖于接口或抽象类。
- 接口隔离原则 (ISP, Interface Segregation Principle):使用多个专门的接口比使用大一的总接口要好。使用多个专门的接口比使用大一的总接口要好
重载与覆盖
重载 (overload)
重载,指函数具有相同的函数名,但这些同名函数具有不同的参数列表 (如:参数个数、参数类型、参数顺序等)。返回值可以相同也可以不同。
重载的实现:编译器根据不同的参数表对同名函数的名称做修饰,如:function func(p:integer) -> integer_func; function func(p:string) -> string_func。对编译器而言,这些同名方法就成了不同的方法,而且它们的调用地址在编译期间就绑定了,可以说是“早绑定”或“静态绑定”。
覆盖/重写 (override)
覆盖存在类中,子类重写从父类继承过来的函数。但函数名、返回值、参数列表都必须和父类一样。当子类的实例对象调用的时候,会先调用自身的,没有再调用父类的。
如果子类覆盖的是基类的虚函数,则可以实现多态。当子类重新定义基类的虚函数后,基类指针可以根据赋给它的不同子类指针动态的调用子类中的该函数,可以做到动态绑定,即多态。
重载与覆盖
- 重载要求函数名相同,但是参数列表不同,返回值可以相同也可以不同;
覆盖要求函数名、参数列表和返回值都相同。 - 在类中,重载是同一个类中不同成员函数之间的关系;
而覆盖则是子类和基类之间不同成员函数之间的关系。 - 重载函数的调用是根据参数列表来决定调用哪个函数;
覆盖函数的调用则根据对象的不同来决定调用哪个函数。 - 在类中,函数重载不能够实现多态;
而子类对基类虚函数的覆盖则可以实现多态。
多态与重载
- 多态:基于对抽象方法的覆盖来实现,当子类重新定义基类的虚函数后,基类根据赋给它的不同子类,动态的调用子类中的该函数。多态可以说是“晚绑定”或“动态绑定”。
- 重载:指函数具有相同的函数名,但这些同名函数具有不同的参数列表。在编译期间就已经绑定调用地址了,是“早绑定”或“静态绑定”。而且,重载只是一种语言特性,一种语法规则,与多态无关,与面向对象也无关。
构造函数与析构函数
构造函数:一种特殊的方法,主要用于在创建对象时初始化对象。
析构函数:与构造函数相反,当对象结束其生命周期 (如对象所在的函数已调用完毕),系统自动执行析构函数。
Python 分别用__init__()和__del__()来实现构造函数和析构函数。
混入类
- 混入类被定义为,一种被设计为通过继承与其它类结合的类,它给其它类提供可选择的接口或功能。
- 从概念上讲,混入类不定义新类,只是提供接口或功能,便于重用;从实现上讲,混入类要求多继承。
- 混入类为多个不相干的子类提供方法实现,便于重用,但不会实例化,并且具体类不能只继承混入类。
- Python 中没有声明混入类的正式方式,Luciano 推荐在名称中加入
Mixin后缀。
1 | class People(object): |
OOP - Python
- 在 Python 中一切都是对象。
- Python 支持多重继承,内置函数
super()可以解决多重继承中父类方法被多次调用的问题。 Python 并没有覆盖 (override) 的概念,从这个角度来说,Python 不支持多态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28class A(object):
def __init__(self):
pass
def __del__(self):
pass
def func(self):
return 'A.func'
class B(A):
def func(self, arg=0):
return 'B.func'
class C(A):
pass
B().func() # B.func
B.func
<function __main__.B.func>
B().func
<bound method B.func of <__main__.B object at 0x7f..>>
C().func() # A.func
C.func
<function __main__.A.func>
C().func
<bound method A.func of <__main__.A object at 0x7f...>>Python 中没有覆盖 (override) 的概念,子类重写父类的方法,其实只是把同名函数绑定到不同的函数对象。
Python 没有像 Java 等语言一样的接口概念,为了解决继承结构中接口和实现的问题,可以人为的设计一些规范。
1
2
3class A(object):
def func(self):
raise NotImplementdError继承
A的子类如果要使用func方法就要先实现,不然抛出异常。纵然如此,A还是可以被实例化的,而且无法强制A的子类必须实现func接口。
Python 虽然没有接口的语法定义,但是还是有抽象类的概念的,使用抽象类实现类似接口的功能:1
2
3
4
5from abc import abstractmethod, ABCMeta
class A(metaclass=ABCMeta):
@abstractmethod
def func(self):
passPython 实现抽象类需要使用 abc 模块,抽象类
A不会被实例化。Python 不支持多态,也不用支持多态,Python 是一种多态语言,崇尚鸭子类型 (duck typing,当看到一只鸟走起来像鸭子、游泳起来像鸭子和叫起来也像鸭子,那么这只鸟就可以被成为鸭子)。鸭子类型不关注对象的类型,而是关注对象的行为 (方法)。鸭子类型像多态一样工作,但是没有继承关系。类与类之间不用共同继承同一个父类,只需要将它们做的像同一种事物,就能够实现:面向接口编程,而不是面向实现编程。
耦合与内聚
耦合
耦合性,也称块间联系,是软件系统结构中模块间相互关联紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。耦合程度取决于各个模块间接口的复杂程度、调用的方式,以及传递的信息。耦合分为七种:
内容耦合:一个模块直接访问另一个模块的内容。具体表现为:
- 一个模块直接访问另一个模块的内部数据。
- 一个模块不通过正常入口而直接转入到另一个模块的内部。
- 两个模块有一部分代码重叠 (该部分代码具有一定的独立功能)。
- 一个模块有多个入口。
公共耦合:一组模块都访问同一个公共数据环境。公共数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。
外部耦合:一组模块访问同一全局简单变量而不是同一全局数据结构,而且不通过参数传递该全局变量的信息。
控制耦合:模块之间传递的不是数据信息,而是控制信息 (如,标志、开关量等),一个模块控制了另一个模块的功能。
标记耦合:调用模块和被调用模块之间传递数据结构而不是简单数据,同时也称特征耦合。标记耦合的模块间传递的不是简单变量,而是像高级语言中的数据名、记录名和文件名等数据结果,这些名字即为标记,其实传递的是地址。
数据耦合:调用模块和被调用模块之间只传递简单的数据项参数。相当于高级语言中的值传递。
非直接耦合:两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的,非直接耦合的模块独立性最强。
耦合是影响软件复杂程度和设计质量的一个重要因素,为提高模块的独立性,应建立模块间尽可能松散的系统,在设计上我们应采用一下原则:若模块间必须存在耦合,应尽量使用数据耦合,少用控制耦合,慎用或有控制地使用公共耦合,并限制公共耦合的范围,尽量避免内容耦合。
内聚
内聚性,又称内联系,指模块的功能强度的度量,即一个模块内容各个元素彼此结合的紧密程度的度量。若一个模块内个元素 (语名之间、程序段之间) 联系的越紧密,则它的内聚性就越高。
所谓高内聚是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一职责原则。
内聚有如下分类 (顺序按照内聚度由弱到强):
偶然内聚:也称巧合内聚,模块内的各部分之间没有联系,或即使有联系也和松散,只是偶然地被凑到一起。
逻辑内聚:把几种相关的功能组合到一起,每次被调用时,由传递给模块的参数来确定该模块应该完成哪种功能。
时间内聚:又称经典内聚,这种模块大多为多功能模块,但模块的各个功能的执行与时间有关,通常要求所有功能必须在同一时间内执行。例如,初始化模块和终止模块。
过程内聚:构件或者操作的组合方式时,允许在调用前面的构件或操作之后,马上调用后面的构件或操作,即使两者之间没有数据进行传递。模块完成多个需要按一定的步骤一次完成的功能。(过程相关—控制耦合)。例如:在用程序流程图设计模块时,若将程序流程图中的一部分划出各自组成模块,便形成过程内聚。
通信内聚:指模块内所有处理元素都在同一个数据结构上操作或所有处理功能都通过公用数据而发生关联 (有时称之为信息内聚)。即指模块内各个组成部分都是用相同的数据或产生相同的数据结构。
顺序内聚:一个模块中各个处理元素和同一个功能密切相关,而且这些处理必须顺序执行,通常一个处理元素的输出是后一个处理元素的输入。顺序内聚的内聚度比较高,但缺点是不如功能内聚易于维护。
功能内聚:模块内所有元素的各个组成部分都为完成同一个功能而存在,共同完成一个单一的功能,模块已不可再分。即模块仅包括为完成某个功能所必须的所有成分,这些成分紧密联系、缺一不可。
功能内聚是最强的内聚,其优点是功能明确。判断一个模块是否功能内聚,一般从模块名称就能够看出。如果模块名称只有一个动词和一个特定的目标 (单数名词),一般来说就是功能内聚,如:“计算水费”、“计算产值”等模块。
模块划分时,要遵循“一个模块,一个功能”的原则,尽可能使模块达到功能内聚。