课件
软件设计原则
设计原则是系统分解和模块设计的基本标准,应用这些原则可以使代码更加灵活、易于维护和扩展。
虽然不同的程序设计语言可能有一些自己语言特点的原则,但是抽象、封装、模块化、层次化和复用这样的原则应该是所有语言都通用的。
抽象
抽象是一种思考和解决问题的方法,它只是关注事物中和问题相关的部分,而忽略其他无关的部分。
例子:假设一个应用程序要连接 SQL Server 数据库:
一种实现方法是应用程序直接使用 SQLServerConnection 类来实例化一个对象,然后在程序中调用它,但是如果我们需要把数据库换成 MySQL,这时,程序中所有 SQLServerConnection 就要全部换成 MySQLConnection,显然数据库的更换对应用程序的修改造成很大影响。
现在换一种方法,提供一个通用的数据库连接接口 DBConnection,应用程序调用这个接口对数据库进行连接,不同的数据库各自具体实现这个接口:
通过这种方式,只要接口不变,数据库发生更改时只会影响具体接口的实现,而不会影响上面的应用程序。
显然抽象是认识复杂事物过程中使用的一种思维工具,可以起到降低复杂性和增强扩展能力的作用。
封装
封装和信息隐藏是把一个软件单元的实现细节进行隐藏,然后通过外部可见的接口来描述它的特性。
需要注意的是单元接口应该设计的尽可能简单,并把单元对于环境的假设和要求降至最低。
模块化
模块化是把一个复杂的系统分解成多个更小的模块,模块是可以组合、分解和更换的单元,模块之间通过接口进行联系,这也是“分而治之”的思想,通过把一个问题分解成多个小的独立而且相互作用的组件,来降低大型软件开发的复杂度。
对软件系统进行模块化分解的原则就是高内聚、低耦合。
- 内聚:所谓内聚是一个模块或子系统内部的依赖程度,描述的是模块内的功能联系,高内聚的模块应该只做而且做好一件事。如果一个模块或子系统的元素都是彼此相关的,而且都执行类以的任务,那么它的内聚性就比较高。如果含有很多彼此不相关的元素,它的内聚就比较低。
- 耦合:是指不同模块之间相互连接的程度。如果两个不同模块之间相对独立,或者有很少的连接依赖,那么其中一个发生变化的时候对另一个产生的影响就很小,反过来如果它们之间有很强的关联,一个发生变化就可能对另一个产生很大的影响。
举例1:某游戏软件的部分类图如下,其中 GameCharacter 代表人物,GameLayout 代表布局,GameArea 代表一个区域,GameAreaConnection 代表两个区域的连接。不同角色的人物可以在一定的区域中移动,当双方相遇之后会进行打斗:
在第一个方案(图2左边的图)中我们把所有的类都放在一个 GameCE 子系统中,但仔细分析就会发现游戏角色和布局环境这两个部分所实现的任务是完全不同的,而且它们的关联也不是非常紧密,根据高内聚的原则,我们可以把它们分成两部分:
其中 Characters 只和游戏角色有关,Environment 只和布局环境有关,而且每一个部分的功能都是单一职责,显然第二个方案(图2右边的图)内聚性更高。
举例2:在一个软件系统中,有三个子系统 A、B、C ,都要访问一个关系数据库。
图3给出的设计方案是三个子系统直接访问数据库,数据库和 A、B、C 三个子系统形成了高耦合的情况,当数据存储方式发生变化时,三个系统都会发生改变。
为了降低这四个部分之间的耦合,在 A、B、C 三个子系统和数据库中间增加一个存储子系统 Storage ,这样就屏蔽了底层数据库的变化对上层子系统产生的影响,当底层的存储机制发生变化的时候,只需要修改存储子系统。如此一来,子系统分解的整体耦合度就降低了。
层次化
在系统被划分成若干模块之后,可以分别在一个模块的内部来处理那些数量已经大量减少的元素,但是为了确保系统的完整和一致,我们同时还必须在系统的层面来处理这些模块之间的关系。
层次关系是一种常见的系统结构,一个系统的层次分解会产生层次的有序集合。这里的层是指一组提供相关服务的模块单元,通常是通过使用另一层的服务来实现本层的功能。
- 分层(Layering):
- 层一定是有序组织的,每一层可以访问下面的层,但是不能访问上面的层,最底层不依赖于任何层,最顶层也不会被任何层来调用;
- 在一个封闭式的结构中,每一层只能访问和它相邻的下一层;
- 在一个开放式的结构中,每一层还可以访问下面更低的其他层次;
- 层的数目不宜过多,不应超过 7±2 层;
- 划分(Partitioning)
- 系统被分解成相互对等的若干模块单元;
- 每个模块之间依赖较少,可以独立运行,如图5中第二层的 B、C、D 和第三层的 E、F、G ;
一个大的系统可以逐层分解,直到所分解的每一个模块可以简单到一个开发人员可以独立实现为止。但是由于不同模块单元之间存在接口,所以每一个模块都增加了处理的开销,过度的划分和分解会增加额外的复杂性。
示例:安卓操作系统层次结构
- 最底层是 Linux 内核,为安卓提供启动和管理硬件以及 Android 应用程序的最基本的软件。
- 内核之上,是一系列共享程序库(系统运行库层),为开发者和类似终端设备拥有者提供必要的核心功能。
- 再之上,是应用框架层,支持第三方开发者之间的交互,使其能够通过抽象方式访问所开发的应用程序需要的关键资源。
- 最上层是应用层,是运行在虚拟机上的 Java 应用程序。
复用
复用(Reuse)是利用某些已开发的软件元素,来生成新的软件系统,其好处在于提高开发效率和软件质量。
软件由不同粒度的复用:
- 源代码复用:代码复用是一种最常见的形式,构件库中的源代码构件具有更高的通用性和可靠性,像 Pyhton 社区中就有大量实用的构件库;
- 软件体系结构复用:是采用已有的软件体系结构对系统进行设计,通常它支持更高层次、更大粒度的一个系统复用;
- 框架复用:是对特定领域中存在的一个公共体系结构及其构件进行复用,Python 就拥有大量的框架,比如说 django、flask、tornado 等;
- 设计模式:设计模式是通过为对象协作提供思想和范例来强调方法的复用,应该说设计模试是一种设计思想,但是不同语言的特性会影响设计模式的实现,比如说 C++语言实现设计模式是充分利用集成和虚函数这样的机制,但是 Python 语言提供了和 C++完全不同的对象模型,而且它有一些特殊的语法,比如说装饰器这个本身就应用了设计模式,所以 Python 在运用和实现设计模式上与其他的一些面向对象语言是不同的。