这一篇内容,我们一起来了解什么是通常所说的面向对象程序设计(Object Oriented Programming)。
在之前几篇教程中,我们了解了类的定义和使用方法。
在类的使用中,包含了多态、封装和继承这三个显著的特点。
这三个特点,其实就是面向对象程序设计的三大特性。
它们让程序设计变得灵活,增加了程序的安全性(封装),具有良好的重用性(继承),易于扩展和维护(多态)。
如果大家对面向过程的程序设计做一些了解,会对这三个特性有更深的体会。
面向过程的程序设计,是通过一个main()函数定义程序要完成的任务,由一系列的子函数组成。
这些子函数,还会细化成更多的子函数,以满足程序的各种需求。
重复这样的过程,就是面向过程的程序设计。
所以,面向过程的程序设计把整个程序视为一系列的命令集合,即一组函数的顺序执行。函数细分为子函数,是为了降低程序的复杂度。
这就好像我们书写论文,main()函数就是提纲,子函数就是论题,子函数细分的子函数就是论点,数据是论据,而函数的计算过程就论证的过程。
论文的这种结构,逐层划分,逐一解决,就是为了降低整篇论文的复杂程度。
如果不进行划分,直接从头至尾书写论文,很显然难以完成。
这样的程序设计易于理解,但是,很明显具有很高的关联性。
在程序设计的过程中,一旦发生需求的改变,很难应对。
而面向对象的程序设计,不是以过程为导向,而是以对象为导向。
程序设计中,会划分出不同的类,通过类来创建出对象。
整个程序是一系列对象的集合,每个对象都可以接收其他对象发过来的消息,并处理这些消息,程序的执行过程就是一系列消息在各个对象之间传递和处理。
面向对象程序设计的优势还体现在它的五个基本原则。
单一职责原则:SRP(Single Responsibility Principle)
是指一个类的功能要单一,具备的特性与类要具有很高的相关性,降低了代码的复杂程度。
开放封闭原则:OCP(Open-Close Principle)
功能模块在扩展性方面应该是开放的而在修改性方面应该是封闭的。
例如饲养动物的示例中,允许增加喂养其它动物的功能,也就是说对于扩展是开放的。
而增加喂养其他动物的功能时,不用对已有的喂养函数进行修改,这是说明对于修改是封闭的。
里氏替换原则:LSP (the Liskov Substitution Principle)
子类应当可以替换超类并出现在超类能够出现的任何地方。
就像俗语说的“龙生龙,凤生凤,老鼠儿子会打洞”,需要打洞了,不管是老鼠爸爸还是老鼠儿子都能胜任。
但是,网上有一个关于鸟类的例子。
在生物学中,企鹅属于鸟类;不过,企鹅不会飞。
这特么就尴尬了。如果我们为鸟类定义飞的方法,企鹅这个子类无法实现这个方法。
很明显这违反了里氏替换原则。
那么是如何解决的呢?
解决方案是:鸟类不包含会飞的方法,而是定义一个会飞的鸟的子类和一个企鹅类,这样飞的方法就变成了子类的扩展,企鹅类也就不需要对飞的方法进行处理,不再违反里氏替换原则。
依赖倒置原则:DIP(the Dependency Inversion Principle)
具体依赖抽象,上层依赖下层。这样的依赖是倒置的。
例如饲养动物的示例中:
- 各个动物类具有抽象的特性,饲养员类是具体的实现。
- 进食是饲养的前提条件,没有进食无需饲养,所以动物类是下层,饲养类是上层。
- 在饲养动物这一功能的具体实现时,依赖的是各个动物进食的特性,没有进食特性,则无法实现饲养。(具体依赖抽象)
- 并且,只要具备进食特性的动物,都可以被饲养,而不是,依赖新增一种饲养方法。(底层不能依赖上层)
接口分离原则:ISP(the Interface Segregation Principle)
接口分离原则指在设计时,多个专用接口优于一个单一的通用接口。每个接口可以为单一的客户程序调用;这样能够避免,当某个客户程序需求变化时,对其他客户程序带来影响。
这就好像家里的电源,如果总电源上,通过一个插座(通用接口)连接所有的电器,当一个电器发生故障需要维修,就需要断掉总电源,导致其他电器受到影响。
那么,如果总电源上分出多个插座,每个插座(专用接口)连接一个电器,维修的时候,就只需要断掉专用的插座电源,其他电器依然能够正常工作。
另外,在程序设计中,大家还要理解耦合和内聚的概念。
这里,我偷懒引用网络上的一段内容,大家自行理解。
耦合
简单地说,软件工程中对象之间的耦合度就是对象之间的依赖性。指导使用和维护对象的主要问题是对象之间的多重依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。
耦合可以分为以下几种,它们之间的耦合度由高到低排列如下:
- 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
- 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
- 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
- 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
- 标记耦合 。若一个模块A通过接口向两个模块B和C传递一个公共参数,那么称模块B和C之间存在一个标记耦合。
- 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
- 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
内聚与耦合
内聚:标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。
总结
程序讲究的是低耦合,高内聚。
就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。
内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。
在进行软件设计时,应力争做到高内聚,低耦合。
转载请注明:魔力Python » Python3萌新入门笔记(29)