C++ 复习教程第三章(设计专业的 C++ 程序)

第4章 —— 设计专业的 C++ 程序


4.1程序设计概述

设计文档的常见布局基本类似,包括两个主要部分:

(1) 将总的程序分为子系统,包括子系统之间的界面和依赖关系、子系统之间的数据流、每个子系统的输入输出和通用线程模型。

(2) 每个子系统的详情,包括类的细分、类的层次结构、数据结构、算法、具体的线程模型和错误处理的细节。

就是 UML 图,这里就不仔细说了。


4.2程序设计的重要性

总结,UML。


4.3C++ 设计的特点

在使用 C++进行设计时,需要考虑 C++语言的一些性质:

  • C++具有庞大的功能集。它几乎是 C 语言的完整超集,此外还有类、对象、运算符重载、异常、模板和其他功能。由于该语言非常庞大,使设计成为一项令人生畏的任务。

  • C++是一门面向对象语言。这意味着设计应该包含类层次结构、类接口和对象交互。这种设计类型与传统的 C 和其他过程式语言的设计竞全不同。第 5 章重点介绍 C++面向对象设计。

  • C++有许多设计通用的、可重用代码的工具。除了基本的类和继承之外,还可以使用其他语言工具进行高效的设计,如模板和运算符重载。第 6 章将详细讨论可重用代码的设计技术。

  • C++提供了一个有用的标准库,包含字符串类、I/O 工具、许多常见的数据结构和算法。所有这些都便于 C++代码的编写。

  • C++语言提供了许多设计模式或解决问题的通用方法。

因此,优秀的设计难能可贵,获取这样的设计需要实践。不要期望一夜之间成长为专家,掌握 C++设计比C++编码更难。


4.4C++ 设计的两个原则

  • 抽象,接口化
  • 重用,方法化

4.1抽象

忽略本质,只在意接口、输入、输出,只看结果;

接口并不决定底层实现,改变实现并不需要改变接口;

象棋棋盘示例:

1
2
3
4
5
6
7
8
9
10
>class ChessBoard
>{
public:
// This example omits constructors, destructor, and assignment operator.
void setPieceAt(size_t x, size_t y, ChessPiece* piece);
ChessPiece* getPieceAt(size_t x, size_t y);
bool isEmpty(size_t x, size_t y) const;
private:
// This example omits data members.
>};

4.2重用

对已存在代码的使用;

去除特殊化,泛化代码;

任何棋类棋盘示例:

1
2
3
4
5
6
7
8
9
10
11
>template <typename PieceType>
>class GameBoard
>{
public:
// This example omits constructors, destructor, and assignment operator.
void setPieceAt(size_t x, size_t y, PieceType* piece);
PieceType* getPieceAt(size_t x, size_t y);
bool isEmpty(size_t x, size_t y) const;
private:
// This example omits data members.
>};

例如,假定要设计一个国际象棋程序:使用一个 EirorLogger 对象将不同组件发生的所有错误都按顺序写入一个日志文件。当试着设计 EirorLogger 类时,你意识到只想在一个程序中有一个ErrorLogger 实例。还要使程序的多个组件都能使用这个 ErrorLogger 实例,即所有组件都想要使用同一个 ErrorLogger 服务。实现此类服务机制的一个标准策略是使用注入依赖(dendency injection)使用注入依赖时,为每个服务创建一个接口,并将组件需要的接口注入组件。因此,此时良好的设计应当使用“依赖注入”模式你必须熟悉这些模式和技术,根据特定设计问题选择正确的解决方案。在 C++ 中,还可以使用更多技术和模式。详细讲述设计模式和技术超出了本书的范围,如果读者对此感兴趣,可以参阅附录 B 给出的建议。


4.5重用代码

注意:重用代码并不意味着复制和粘贴已有的代码,但实际含义刚好相反:重用代码,但不重复代码;


5.1关于术语的说明

在分析代码重用优缺点前,有必要指出所涉及的术语,并将重用代码分类。有三种可以重用的代码:

  • 过去编写的代码;
  • 同事编写的代码;
  • 当前组织或公司以外的第三方编写的代码;

所使用的代码可通过以下几种方法来构建:

  • 独立的函数或类,当重用自己或同事的代码时,通常会遇到这种类型;
  • 库,库是用于完成特定任务(例如解析 XML)或者针对特定领域(如密码系统)的代码集合。在库中经常可以找到其他许多功能,如线程和同步支持、网络和图像。
  • 框架(Framework),框架是代码的集合,围绕框架设计程序。例如,微软基础类(Microsoft Foundation Classes, MFC)提供了在 Microsoft Windows 中创建用户界面应用程序的框架。框架通常指定了程序的结构。

注意:

程序使用库,但会适应框架。库提供了特定功能,而框架是程序设计和结构的基础。

应用程序编码接口(API)是另一个经常出现的术语。API 是库或为特定目的而提供的接口。例如,程序员会经常提到套接字 API,这是指套接字联网库的公开接口,而不是库本身

注意:

尽管人们将库以及 API 互换使用,但是两者不是等价的。库指的是“实现”,而 API 指的是 “库的公开接口”。

为简洁起见,本章剩余部分用术语“库”表示任何可重用的代码,它事实上可能是库、框架或是同时编写的随机函数集合。


5.2决定是否重用代码

重用代码的优点:

  • 节省时间和成本;
  • 不需要额外的设计;
  • 不需要调试;
  • 代码安全性更高;
  • 库会持续改进;

重用代码的缺点:

  • 需要花费时间了解接口和正确使用方法;
  • 代码功能未必完全吻合;
  • 代码支持问题可能遇到问题;
  • 代码使用许可问题;
  • 跨平台可移植性问题;
  • 对代码质量与安全性的信任问题;
  • 版本更新可能会出现致命问题;
  • 使用纯粹二进制库时,将编辑器升级为新版本会导致问题;

熟悉了重用代码的术语和优缺点后,就可以决定是否重用代码。通常,这个决定是显而易见的。例如,如果想要用 C++ 在 Microsoft Windows 上编写图形用户界面(GUI), 应该使用 MFC(Microsoft Foundation Class)或Qt 等框架。你可能不知道如何在 Windows 上编写创建 GUI 的底层代码,更重要的是不想浪费时间去学习。在此情况下使用框架可以节省数年的时间。

然而,有时情况并不明显。例如,如果不熟悉某个库或框架,并且只需要其中某个简单的数据结构,那就不值得花时间去学习整个框架来重用某个只需要花费数天就能编写出来的组件。

总之,这个决定是根据特定的需求做出的选择。通常是在自己编写代码所花时间和查找库并了解如何使用库来解决问题所使用时间之间的权衡。应该针对具体情况,仔细考虑前面列出的优缺点,并判断哪些因素是最重要的。最后,可随时改变想法,如果正确处理了抽象,这并不需要太多的工作量。


5.3重用代码的策略

当使用库、框架以及同事或自己的代码时,应该记住一些指导方针:

  1. 理解功能和限制限制因素
    • 花点时间熟悉代码,对于理解其功能和限制因素而言都很重要。可从文档、公开的接口或 API 开始,理想情况下,这样做足以理解代码的使用方式。然而,如果库未将接口和实现明确分离,可能还要研究源代码。此外,还可与其他使用过这些代码或能解释这些代码的程序员交流。
  2. 理解性能
    • 了解库或其他代码提供的性能保障很重要。即使某个程序对性能不敏感,也应该确保使用的代码在具体的使用中性能不会太糟。
  3. 大 O 表示法
    • 程序员经常使用大0 表示法(Big-0 Notation)讨论并记录算法和库的性能。 注意:大 O 表示法仅适用于速度依赖于输入的算法,不适用于没有输入或者运行时间随机的算法。实际上,大多数算法的运行时间都取决于输入,因此这个限制并不重要。

image-20240209135140519

  1. 理解性能的几点提示

    • 略,见书。
    • 总结一句就是,你不测试就永远不知道这个库到底性能高不高。
  2. 理解平台限制

    • 在开始使用库代码之前,一定要理解运行库的平台。这看上去是显而易见的,但即使是那些号称跨平台的库,在不同的平台上也会有微妙差别;此外,平台不仅包括不同的操作系统,还包括同一操作系统的不同版本。 不要以为库一定会向前或先后兼容;
  3. 理解许可证和支持

    • 使用第三方的库常会带来复杂的许可证问题。为使用第三方供应商提供的库,有时必须支付许可证费用。还可能有其他的许可限制,包括出口限制。此外,开放源代码库有时会要求与其有关的任何代码都公开源代码;
  4. 了解在哪里寻求帮助

    • 首先参考库自带的文档。如果库被广泛使用,如标准库或 MFC, 就应该能找到与此主题相关的优秀书籍。实际上,本书的第 1A21 章都讲述标准库。如果某个特定问题在手册或产品文档中没有提及,可搜索 Web。在选择的搜索引擎中输入问题来寻找讨论这个库的 Web 页面。例如,当查找短语 “introduction to C++ Standard Library” 时,会找到与 C++和标准

      库有关的数百个站点。此外,许多站点包含关于特定主题的新闻组或论坛,可注册并寻找答案。

  5. 原型

    • 当首次使用某个新库或框架时,最好编写一个快速原型。测试代码是熟悉库功能的最好方法。应该考虑在程序设计之前测试库,这样就可以熟悉库的功能和限制。这种实际检验还可判断库的性能特征。即使原型应用程序与最终应用程序没有任何相似之处,花费在原型上的时间也不会浪费。不要觉得编写实际应用程序的原型很难,可编写一个虚拟程序来测试想使用的库功能,这样做是为了让自己熟悉库。

5.4绑定第三方应用程序

项目可能包含多个应用程序。或许需要 Web 服务器前端来支持新的电子商务基础设施。可将第三方应用程序(例如 Web 服务器)与软件绑定。这种方法将代码重用发挥到了极致,因为重用了整个应用程序。当然,使用库的那些忠告和指导方针也适用于绑定第三方应用程序,应该特别注意自己的决定所涉及的法律和许可证问题。

注意:

将第三方应用程序与分发的软件绑定之前,应当请教专门处理知识产权问题的法律专家。


5.5开放源代码库

开放源代码库是一种日益流行的可重用代码类型。开放源代码(open-source)通常意味着任何人都可以查看源代码。关于分发软件时包含源代码,有正式的定义和法规,但最重要的是,任何人(包括你)都能查看开放源代码软件的源代码。注意开放源代码不仅适用于库,实际上最著名的开放源代码产品可能是 Android 操作系统。Linux 操作系统是另一个著名的开放源代码操作系统。Google Chrome 和 Mozilla Firefbx 是两个开放源代码的著名 Web 浏览器。


5.6C++ 标准库

C++程序员使用的最重要的库就是 C++标准库。

C++ 提供了比 C 更好的字符串以及 I/O 支持。尽管 C 风格的字符串和 I/O 例程在 C++中仍然有效,但应该避免使用它们,而是使用 C++字符串(详见第 2章)和 I/O 流(详见第 13 章)。

设计标准库时优先考虑的是功能、性能和正交性(orthogonality)。使用标准库可获得巨大好处。可回顾在链表或平衡二叉树实现中跟踪指针错误,或者调试不能正确排序的排序算法,如果能够正确使用标准库,几乎不需要再编写这类代码。第 16 21 章将提供有关标准库功能的信息;


4.6设计一个国际象棋程序

本节通过一个简单的国际象棋程序系统介绍 C++程序的设计方法。为提供完整示例,某些步骤用到了后面几章讲述的概念。为了解设计过程的概况,可现在就阅读这个示例,也可以在学完后面章节后返回头来学习;


6.1需求

在开始设计前,应该弄清楚对于程序功能和性能的需求。理想情况下,这些需求应该是以需求规范(requirements specification)形式给出的文档。国际象棋程序的需求应该包含下列类型的规范,当然实际的需求规范应该比下面的内容更详细,条目更多:

  • 程序支持标准的国际象棋规则。
  • 程序提供基于文本的界面。
    • 程序以纯文本形式提供棋盘和棋子。
    • 玩家通过输入代表位置的数字在棋盘上移动棋子。

6.2设计步骤

  1. 将程序分割为子系统;

image-20240209140908334

image-20240209141005292

  1. 选择线程模型;

  2. 指定每个子系统的类层次结构;

image-20240209141103026

image-20240209141119716

  1. 指定每个子系统的类、数据结构、算法和模式;

image-20240209141142275

image-20240209141207029

  1. 为每个子系统指定错误处理;

image-20240209141229352


4.7本章小结

本章介绍了专业的 C++设计方法。软件设计是任何编程项目中重要的第一步。你还学习了使得设计变得困难的一些 C++特性,包括 C++关注的面向对象、庞大的功能集和标准库、编写通用代码的工具。这些信息可让程序员更好地处理 C++设计;

本章介绍了两个设计主题:抽象和重用。抽象(或将接口与实现分离)的概念贯穿全书,所有的设计工作都应该以此为指导方针。重用的概念(无论是代码还是设计)在实际项目和本书中经常会出现。C++设计应该包含代码的重用(以库或框架的形式)以及思想和设计的重用(以技术和模式的形式)。 应该尽可能编写可重用的代码。此外还要记住权衡重用的优缺点和重用代码的特定方针,包括理解功能和限制、性能、许可证、支持模式、平台限制、原型和帮助资源。你还学习了性能分析和大 O 表示法。现在你已经理解了设计的重要性和基本的设计主题,并做好了学习本书第 Ⅱ 部分其余章节的准备。第 5 章将讲述在设计中使用 C++面向对象特性的策略;


C++ 复习教程第三章(设计专业的 C++ 程序)
http://example.com/2024/03/14/C++ 复习教程第四章(设计专业的 C++ 程序)/
作者
yanhuigang
发布于
2024年3月14日
许可协议