870920 Menu

SwingCoder之面向对象分析、设计及软件研发·3

设计常识

使用C++进行软件开发,设计远比编码复杂而困难。无论项目大小,在开始编码之前,务必要进行全面的分析与思考。设计要以明确、清晰的文档来呈现,文字说明、注解、备忘、图示、表格、流程图、UML图、思维导图等等都是必不可少的文档元素。设计的主要流程与步骤:

 划分子系统:理清子系统的功能,各个子系统之间的关系与接口,这个步骤中无需考虑特定的数据结构及算法,也无需考虑类群和类系。

 明确线程模型:绝大多数程序都需要同时执行多个任务(并发执行),此时必须使用和考虑多线程编程的问题。在此步骤中,需粗略预算所需的线程数量与线程之间的交互方式。尽量避免共享数据,如无法避免,则提前规划好加锁和互斥。

 类系和类群:为每个子系统确定其类层次结构和类的交互关系(规划类系和类群)。简单说就是抽象共性,给出基类、派生类的基本框架与接口,在此基础上,规划出具有关系的一组或多组类群。

 选用第三方库和类:使用之前要全面了解和学习,理解其设计策略与目的,并进行必要的测试。

 设计类:包括类的数据结构(has-a聚合的对象,所持有的容器对象、数组,各种内置类型的变量,等等)、算法及设计模式。在此步骤中,要最大限度的考虑设计模式的运用及类的接口(public成员函数)。抽象和重用的指导思想,在此步骤也体现的最彻底。只要有可能,尽量使用泛型编程(类模板与函数模板)。注意:泛型编程并不仅仅只是模板技术的运用,比如:C++支持void*类型,可将任何类型的堆对象转换为void*指针变量(尽管这并非类型安全的,并且,销毁void*指针之前务必要转换为对应的类型)。

 错误及异常处理:此步骤中,要给出子系统和类的错误处理,包括系统错误和用户错误(如无效输入或无效操作),应该确定是否每个子系统和类均需要进行错误和异常处理,如进行处理,则需考虑如何产生消息或抛出异常,谁来处理错误和异常。错误处理的通用法则是处理一切,要尽最大努力考虑所有可能出错的情况,如果忽略某种可能性,则迟早会暴露出一个或多个Bug。系统错误和用户错误要区别对待,系统错误往往采用记录错误日志的方式(可使用一个专门记录错误的单例日志类,比如ErrorLogger),而用户错误则往往需要给出提示信息。

以下是设计中的一些基本常识:

 类是解决实际问题的过程中所需的一些概念与成分。

 确定需要哪些类型,为每个类型提供一组完整的操作,明确每个类的职责。

 抽象类的层次结构是一种表述概念的清晰而强有力的方法。

 一个系统展现给用户的应该是一个抽象类的层次结构,其实现所用的是一个传统的层次结构。

 组合与继承:提取类型之间的共性与差异性,利用封装和继承来明确表示类之间的层次和依赖关系。

 关于模板:确定算法,将算法参数化,提取参数使之成为更抽象和更通用的“新数据类型”。

 不必在了解所有语言细节之后才开始写程序,在此之前依然能够写出好的程序。

 更应该思考和提炼程序设计技术,而不是语言特性。

 编译器首先进行预处理。预处理之后的结果为编译单元。可用源文件和编译单元来区分程序员看到的东西和编译器所看到的东西。连接器将编译单元组织为一个整体,即程序,也即静态连接。程序运行后,允许加入并执行新的代码,这种形式称为动态连接。

 程序项目由一些文件组成,这种组织形式为物理结构,划分物理结构的依据是程序本身的逻辑结构(功能模块)。物理结构不必与逻辑结构完全一致,但应尽量保持形式和结构上的统一。

 软件开发中最基本的问题就是复杂性。只存在一种对付复杂性的基本方法:分而治之、化一繁为多简。如果一个问题可以分成两个能分别处理的子问题,这个划分就已经解决了问题的一半。这些是分析阶段必须要慎重考虑和权衡的事情,以便于确定代码的组织与结构。

 程序需要有一个清晰的结构,以便于阅读、理解、修改、调试、移植、维护、扩展、重构。

 分析与设计密不可分。程序设计旨在创建一种清晰且相对简单的内部结构。这些结构,可用一组类来描述,典型和简化的情况是忽略这些类的private部分,并给出类之间的相互关系(无关性、临时使用、聚合、组合、继承、多重继承)。同时需仔细考虑多线程并发性、名字空间、类模板、函数模板、全局函数、静态函数、全局数据的使用与安排。

 软件开发是一个不断重复和递进的过程,既无开始,亦无结束。每次经历某个阶段,都必须考虑到下次经历该阶段,因此,规范而清晰的代码(包括注释)无比重要。

 开发过程的四个最主要阶段:分析、设计、实现与维护。这些阶段并无严格的区分且不断迭代、彼此兼顾。试验、修改、调试、除错、测评、文档、管理。所有这些,构成软件“维护”这个概念。

 要特别注意以上所有阶段和步骤中不收敛的改变和重复将导致项目的无限期拖延,更要注意不要让细节上的变化与重复淹没了思想上的创新与灵感。如果某次改变和重复并没有产生实质性的进步,那么干脆放弃。

 设计和编写类就是制作具体的零部件。而装配工作同样不容忽视。如果只想打造一辆精致的车,那么必须按照建设一个大型汽车制造厂的目标来规划和实施。提前考虑好各种流水线和装配车间,一定比直接面对一堆零件更容易下手。这也是使用类库的最基本思路。

 无论设计还是开发,最高准则:使之简单,再简单。简单的原则是灵活、抽象、能够适应更多不可预知的变化。特别是:在一系列重复和改变之后,依然保持简单。

 设计的单位是一批类的集合,而不是单个孤立的类。可用名字空间来包裹一个设计单位(如有必要)。

 某个类中组合另一个类的对象,是使用指针,还是使用栈对象,一个原则是:该数据成员是否需要改变自身的值。如需改变,则声明为指针(比如可为nullptr,或指向另一个堆对象),否则,声明为栈对象。不管该对象是否改变,如果整个类的生存期内一直使用同一个对象,则使用引用更好。

 类中的引用对象或传入本类的指针也可理解为与其他类共享该对象。此时需注意并发的读写问题。

 信息隐藏对于良好的软件工程至关重要。面向对象编程中,对象具有信息隐藏的性质,对象与对象之间的通信(消息),通过对象所属的类所定义的接口来进行,这些接口亦即该对象的行为(成员函数),而隐藏的信息则是对象的属性(数据成员)。

 面向对象设计最困难的部分是将系统分解成对象集合,要考虑诸多因素:封装、粒度、依赖关系、灵活性、性能、扩展和复用等等,这些因素影响系统的分解,并且通常还是互相冲突的。可事先写出问题描述,挑出其中的名词和动词,进而创建相应的类和成员函数。也可以从系统的协作和职责、依赖关系等方面入手,或者对现实世界进行建模,再将分析时发现的对象转换到设计中。也就是说,设计方式是灵活多变的,没有定式。