- 引言
- 本书涉及的内容
- 第 1 部分 Python 开发入门
- 第 1 章 Python 入门
- 第 2 章 开发 Web 应用
- 第 3 章 Python 项目的结构与包的创建
- 第 4 章 面向团队开发的工具
- 第 5 章 项目管理与审查
- 第 6 章 用 Mercurial 管理源码
- 第 7 章 完备文档的基础
- 第 8 章 模块分割设计与单元测试
- 第 9 章 Python 封装及其运用
- 第 10 章 用 Jenkins 持续集成
- 第 11 章 环境搭建与部署的自动化
- 第 12 章 应用的性能改善
- 第 13 章 让测试为我们服务
- 第 14 章 轻松使用 Django
- 第 15 章 方便好用的 Python 模块
- 附录 A VirtualBox 的设置
- 附录 B OS(Ubuntu)的设置
8.1 模块分割设计
应用是由功能集合而成的。功能则要通过函数、对象等的相互作用来实现。在 Python 中,函数、类等(以下统称为组件)整合在模块里,模块又整合在程序包里。设计的第一步是设计功能,然后才轮到实现功能的各个组件。
8.1.1 功能设计
在功能设计阶段,我们要敲定即将开发的应用包含哪些功能,明确各功能的输入输出。
从应用的角度看,输入输出不仅包括用户能看到的这部分,还涵盖了与外部相关系统的接口、向数据库保存的数据等。
首先我们要给功能写出一个方案。所谓方案,就是描述用户在使用功能时会与系统进行何种交互的文本。我们要通过这一步明确用户向系统输入的内容,以及系统该向用户显示的内容。
将方案所示的整个流程画成图,即为页面迁移图。这里要写下各个页面的功能。作为一款 Web 应用,应当包含下面几项。
· URL
· HTTP 方法
· 安全(登录、权限等)
· 输入
o 用户输入
o 从数据库或文件读取
o 外部系统发来的内容
· 输出
o 页面输出
o 向数据库或文件写入
o 外部系统的调用
另外,要明确输入和输出的对应关系。连接输入与输出是功能的职责。在功能测试中,我们要查看的也是这些输入和输出。
通过功能设计,我们确定了即将开发的应用应当怎样运作。做完了这一步,接下来就是设计“如何实现这些功能”。简单的功能通常可以一步到位,但在实际开发中,绝大部分功能要么复杂,要么必须与多个功能协同工作。对付复杂的功能时切忌强求一步到位,最好将其视为多个部分(组件)的组合。将复杂功能分割成组件的好处在于可以重复使用同一组件来完成相同的工作,从而有效避免应用内的矛盾。
下面,我们将对构成 Web 应用的组件进行了解。随后再来学习如何将功能分解成组件。
8.1.2 构成 Web 应用的组件
将功能分解成组件需要用到一个指标,我们将这个指标称为软件架构。
Web 应用架构中最有名的当属 MVC(Model View Controller,模型-视图-控制器)了。 =MVC 根据职责不同对 Web 应用的互动(应用与用户关联的)部分进行了分割。M 是 Model,它负责功能的主要逻辑与数据;V 是 View,负责将 Model 以用户能理解的形式显示出来;C 是 Controller,它的工作是根据用户的输入将 Model 和 View 联系起来。
近来的 Web 应用框架已经基本上集成了 Controller 的功能。与此同时,View 则更多地被分割成 HTML 模板和显示逻辑两部分。比如,Django、Pyramid 等框架就很少需要明确写出 Controller,绝大部分情况都是 Model、View、Template 结构。
Model 部分在 MVC 中很少被提及。不过,当应用不是单纯的 Web+DB 形式时,其必然会与外部系统存在某种协作。另外,虽然绝大部分 Model 会被永久保存,但不可否认有些 Model 只是为了构成功能而存在的。鉴于这些因素,我们把 Model 分成 ApplicationModel 和 DomainModel 两类来考虑。DomainModel 是拥有持久(保存在文件或数据库中)状态的模型,而 ApplicationModel 是用于构成功能的模型,虽然它们也能具有状态,但这些状态通常不会被永久化。
现在来总结一下构成 Web 应用的组件。由于 Controller 可以完全交给框架来处理,所以构成功能的组件应如图 8.1 所示。
图 8.1 Web 应用构架
· View
它的工作就是接受请求然后作出响应。输入主要为请求的传值参数,输出则是响应体。它会检查用户输入的数据,另外还会加载模型以及调用ApplicationModel。最后,它会将结果对象转换成可以显示给用户的HTML,生成响应体。
· DomainModel
拥有持久状态的对象。大部分情况下,对象的状态保存在RDBMS中。Python上能使用的O/R映射工具(Object-Relational Mapper)以SQLAlchemy最为有名。另外,它还具有能改变自身状态的业务逻辑(Business Logic)。
· ApplicationModel
没有持久性,其状态是暂时的。状态大多只能维持Web应用的一次请求,或者一定程度上完整的多个页面迁移期间。它们通过一般的类、模块中的函数来实现。经View调用后,执行其对应的各个DomainModel内定义的方法。它们大多不进行具体的处理,只负责传递处理的流程。也正是因为这个,它们依存的模块通常较多。
· ServiceGateway
表示外部服务。其作用是为了让Web API的简单封装、邮件发送、启动其他处理等直接依存于外部系统的内容要能够不包含在Model和View之中。
· Utility
它们大多都很小,不具备状态,输入只有函数传值参数,输出只有返回值。当然,它也可以拥有多种形式的输入,以对象状态维持自身直到最终调用方法,但输入输出要符合前面所说的要求,在该对象内完成所有处理。
一个功能对应一个 View。
ApplicationMode 和 DomainModel 被 View 调用。如果 DomainModel 只有一种,那么程序将通过 View 直接调用 DomainModel。当同时关联着多个 DomainModel 时,程序则要通过 View 调用 ApplicationModel。
ServiceGateway 的作用就是一个简单的封装,它里面不能包含应用固有的内容。ApplicationModel 则要使用 ServiceGateway 进行应用固有的处理。
NOTE
SQLAlchemy
它是连结对象与 RDBMS 的 O/R 映射工具库。虽然 SQLAlchemy 并不是 Python 唯一的映射工具(其他还有 SQLObject、Storm 等),但相比之下它的功能最为丰富,文档也相对齐全。像 Pyramid、Flask 等非全栈式(不具备 O/R 映射工具)框架的说明大多以使用 SQLAlchemy 为前提,这在如今的 Web+DB 应用开发中已经逐渐成为了一条不成文的规定。
8.1.3 组件设计
接下来我们把功能分割到各个组件当中。
◉ 根据输入设计 View
首先对付输入。作为一款 Web 应用,输入的主要来源应该是用户输入的数据。除此之外,已存在的数据和从外部系统获取的数据都属于输入。
用户输入的数据由 View 接收。另外,View 还负责向用户传递数据。交给模板的内容要由 View 进行输出。在 View 的内部会调用 ApplicationModel 或 DomainModel,这些 Model 也是输入之一。还有,由于 View 没有状态可言,所以大多数情况以函数(以下称 View 函数)形式实现。
◉ 初步设计与 View 关联的 ApplicationModel
对 View 函数而言,函数传值参数是输入,返回值是输出。另外,它只关联一种 Model。
这一阶段我们先不考虑 DomainModel 的相关问题,先给每个 View 函数定义一个 ApplicationModel。View 函数和 ApplicationModel 建议根据功能名来命名,比如“{ 功能名 }_view、{ 功能名 }Model”。除此之外,还要给 ApplicationModel 预先定义一个方法,名称同样根据功能名来起。
◉ DomainModel 的设计
接下来定义 DomainModel(View 和 DomainModel 的定义可以并行进行。实际上,在设计 View 之前就可以一定程度上对 DomainModel 进行定义了)。由于 DomainModel 有状态,所以要用类来定义。
定义好 DomainModel 的类之后就要考虑方法了。要明确各个方法是如何改变 DomainModel 的状态的。这些状态要定义成属性。另外,如果要通过调用方法来改变其他相关 DomainModel 的状态,需要在目标 DomainModel 里定义方法,然后调用目标 DomainModel 里的方法。切记不可以直接改变其他 DomainModel 的状态。
◉ 关联 ApplicationModel 和 DomainModel
接下来给 ApplicationModel 和 DomainModel 添加关联。在 ApplicationModel 的方法中定义需要调用的 DomainModel。然后检查功能的输入输出是否满足要求。
◉ 整理 ApplicationModel
当 ApplicationModel 只关联着一个 DomainModel 时,应当删除该 ApplicationModel,让 View 直接调用 DomainModel。在 ApplicationModel 中,对象 Domain 模型一致地要整合成一个类。整合好之后需要立刻给类命名。如果无法确定类名,就尽量不要整合。
◉ 从组件设计转入实现
到这里,我们已经将功能分割到了各个组件之中。接下来就是把它们向 Python 的模块、程序包一步步整合了。
8.1.4 模块与程序包
现在我们根据目的把组件整合成模块或程序包。开发 Web 应用时,要每一个功能制作一个程序包,功能内的各个组件(View、Model 等)分别制成模块。在 Python 中,各个组件由类和函数来实现。
NOTE
我们来整理一下 Python 的模块与程序包。
模块
Python 的源码文件就是一个个模块。里面整合着多个类、函数、变量等内容。虽然我们可以把处理写到模块内,但当我们开发的应用由多个模块构成时,最好只让它作为一种函数和类的集合体出现。
程序包
整合在一起的多个模块。它就是把程序包内包含的模块文件与 __init__.py 整合到了一个单独的目录下。
命名空间程序包
程序包的一种比较特殊的形式,用来向多个位置安装相同名称的程序包。因为只有在分配库文件的时候才会用到它,所以一般应用都与它无缘。
◉ 项目的文件结构
以 MVC 架构为基准时,项目的实际文件结构如下所示。
project/ +-- __init__.py +-- views.py +-- models.py +-- services/ | +-- __init__.py | +-- twitter.py | +-- twitpic.py +-- utils.py +-- templates/ +-- tests/ +-- __init__.py +-- test_models.py +-- test_views.py +-- test_services.py +-- test_utils.py
○ project/__int__.py
启动应用所需的处理都写在这里,比如 WSGI 应用的入口点等。另外,models 的导入、数据库连接的初始化、设置文件的读取等都在这里进行。
○ project/views.py 以及 project/utils.py
分别实现 View 和 Utility。
○ project/models.py
实现 DomainModel 和 ApplicationModel。另外,连接 DB 所需的模块等也要事先导入到这里。
○ project/services/
在存在多个 services 时,由程序包进行整合。
○ project/templates/
存放 HTML 模板的位置。
○ project/tests/
存放单元测试(Unit Test)的位置。
另外,当项目规模非常大的时候,会由多个 project 合在一起形成一个网站。比如用户种类不同(终端用户与服务提供方的用户)或 DomainModel 完全分离时。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论