软件体系结构:定义及风格
|
Software Architecture:
Definition, Common Software Architectural Style and more. |
撰文 / 曾毅
软件体系结构的引入【The beginning of Software Architecture】
软件设计自从计算机诞生之日起,就存在着显式或是隐式的危机。而1968年,在Garmish召开的国际软件工程会议上人们迫切地感到了软件危机给计算机软件产业的发展带来的巨大阻力。软件危机的两个最大的问题便是:随着计算机软件技术的日新月异,软件的规模越来越大,软件复杂度越来越高。伴随着这两个问题的日益突出,整个软件系统结构的设计与规格说明便显得比在早期软件开发中占有重要地位的算法选择和计算问题的数据结构更为重要。代码级别的软件复用已经远远不能满足大型软件开发的需求。由此便引入了软件体系结构这一概念。
软件体系结构的定义【Definition of Software Architecture】
从抽象角度来说,软件体系结构包含对于用于构建系统的元素,元素之间的互操作,指导系统构成的模式以及模式上的约束的描述。大体上说来,一个特定系统风格就是由组件集以及各个组件集之间的交互定义的。这些软件系统风格便可以用在大型系统设计中。
虽然人们在很多年前就认识到了软件体系结构的设计使整个软件开发中的关键元素,但是对于软件体系结构的定义直至今日也没有达成一个共识。以下是在软件体系结构理论发展的若干年中比较有影响力的定义:
Dewayne Perry和A1ex Wolf的定义:软件体系结构是具有一定形式的结构化元素,即组件的集合,包括处理组件、数据组件和连接组件。处理组件负责对数据进行加工,数据组件是被加工的信息,连接组件把体系结构的不同部分组组合连接起来。
这一定义注重区分处理组件、数据组件和连接组件,这一方法在其他的定义和方法中基本上得到保持。
Mary Shaw和David Garlan在Software Architecture: Perspectives on an Emerging Discipline一书中指出:软件体系结构是软件设计过程中的一个层次,这一层次超越计算过程中的算法设计和数据结构设计。体系结构问题包括总体组织和全局控制、通讯协议、同步、数据存取,给设计元素分配特定功能,设计元素的组织,规模和性能,在各设计方案间进行选择等。软件体系结构处理算法与数据结构之上关于整体系统结构设计和描述方面的一些问题,如全局组织和全局控制结构、关于通讯、同步与数据存取的协议,设计组件功能定义,物理分布与合成,设计方案的选择、评估与实现等。
Bass,Ctements和Kazman在Software Architecture in Practice一书中给出如下的定义:一个程序或计算机系统的软件体系结构包括一个或一组软件组件、软件组件的外部的可见特性及其相互关系。其中,“软件外部的可见特性”是指软件组件提供的服务、性能、特性、错误处理、共享资源使用等。
通过对若干定义的学习和研究,我将为软件体系结构下一个这样的定义:软件体系结构为软件系统提供了结构、行为和属性的高级抽象,由构成系统的元素描述、这些元素的相互作用、指导元素集成的模式以及这些模式的约束组成。软件体系结构不仅指定了系统的组织结构和拓扑结构,并且显示了系统需求和构成系统的元素之间的对应关系,提供了一些设计决策的基本原理,是构建于软件系统之上的系统级复用。
软件体系结构的影响【Definition of Software Architecture】
软件体系结构贯穿于软件研发的整个生命周期内,具有重要的影响。这主要从以下三个方面来进行考察:
(1) 利益相关人员之间的交流:软件体系结构是一种常见的对系统的抽象,代码级别的系统抽象仅仅可以成为程序员的交流工具,而包括程序员在内的绝大多数系统的利益相关人员都借助软件体系结构来进行彼此理解、协商、达成共识或者相互沟通的基础。
(2) 系统设计的前期决策:软件体系结构是我们所开发的软件系统最早期设计决策的体现,而这些早期决策对软件系统的后续开发、部署和维护具有相当重要的影响。这也是能够对所开发系统进行分析的最早时间点。
(3) 可传递的系统级抽象:软件体系结构是关于系统构造以及系统各个元素工作机制的相对较小、却又能够突出反映问题的模型。由于软件系统具有的一些共通特性,这种模型可以在多个系统之间传递,特别是可以应用到具有相似质量属性和功能需求的系统中,并能够促进大规模软件的系统级复用。
常用软件体系结构
【Common Software Architectural Style】
一个小型的软件可能具有一种软件体系结构,而大型的软件一般由多种软件体系结构组成,软件体系结构没有定性的说只有几种风格,但是经过长期的大型软件设计与分析,人们总结出了一些最为常用的软件体系结构风格,总共有五种,分别是:
◆数据流风格【Data Flow Style】
数据流风格的体系结构中,我们可以在系统中找到非常明显的数据流,处理过程通常在数据流的路线上“自顶向下、逐步求精”,并且,处理过程依赖于执行过程,而不是数据到来的顺序。比较有代表性的是批作业序列风格、管道/过滤器风格。
图1:管道/过滤器风格的软件体系结构
在管道/过滤器风格的软件体系结构中,每个组件都有一组输入和输出,组件读输入的数据流,经过内部处理,然后产生输出数据流。这个过程通常通过对输入流的变换及增量计算来完成,所以在输入被完全消费之前,输出便产生了。因此,这里的组件被称为过滤器,这种风格的连接器就象是数据流传输的管道,将一个过滤器的输出传到另一过滤器的输入。此风格特别重要的过滤器必须是独立的实体,它不能与其它的过滤器共享数据,而且一个过滤器不知道它上游和下游的标识。一个管道/过滤器网络输出的正确性并不依赖于过滤器进行增量计算过程的顺序。编译器系统就具备典型的管道系统风格的体系结构。在该系统中,一个阶段(包括词法分析、语法分析、语义分析和代码生成)的输出是另一个阶段的输入。
管道/过滤器风格的软件体系结构具有许多很好的特点:
(1) 使得软组件具有良好的隐蔽性和高内聚、低耦合的特点;
(2) 允许设计者将整个系统的输入/输出行为看成是多个过滤器的行为的简单合成;
(3) 支持软件复用。
(4) 系统维护和增强系统性能简单。新的过滤器可以添加到现有系统中来;旧的可以被改进的过滤器替换掉;
(5) 允许对一些如吞吐量、死锁等属性的分析;
(6) 支持并行执行。每个过滤器是作为一个单独的任务完成,因此可与其它任务并行执行。这比下面将要阐述的一种“主-子程序风格”的单线程操作要灵活得多。
这种系统结构的弱点是:
(1) 通常导致进程成为批处理的结构。这是因为虽然过滤器可增量式地处理数据,但它们是独立的,所以设计者必须将每个过滤器看成一个完整的从输入到输出的转换。
(2) 不适合处理交互的应用。当需要增量地显示改变时,这个问题尤为严重。
(3) 因为在数据传输上没有通用的标准,每个过滤器都增加了解析和合成数据的工作,这样就导致了系统性能下降,并增加了编写过滤器的复杂性。
◆调用/返回风格的体系结构【Call-and-Return Style】
调用/返回风格的体系结构在过去的30年之间占有重要的地位,是大型软件开发中的主流风格的体系结构。这类系统中呈现出比较明显的调用/返回的关系。调用/返回风格在常用软件体系结构风格中内涵是比较丰富的,分为:主-子程序风格,面向对象概念中的对象体系结构风格,以及层次型系统风格三种子风格。
图 2:主-子程序风格的体系结构
主-子程序风格的体系结构是一种经典的编程范型,主要应用在结构化程序设计当中。这种风格的主要目的是将程序划分为若干个小片段,从而使程序的可更改性大大提高。主-子程序体系结构风格有一定的层次性,主程序位于一层,下面可以再划分一级子程序,二级子程序甚至更多。需要特别注意的是主-子程序体系结构风格是单线程控制的。同一时刻只有一个孩子结点的子程序可以得到父亲结点的控制。总结一下主-子程序体系结构风格的特点:
(1)由于单线程控制,计算的顺序得以保障。
(2)并且有用的计算结果在同一时刻只会产生一个。
(3)单线程的控制可以直接由程序设计语言来支持
(4)分层推理机制:子程序的正确性与它调用的子程序的正确性有关。
对象风格的体系结构:抽象数据类型概念对软件系统有着重要作用,目前软件界已普遍转向使用面向对象系统。这种风格建立在数据抽象和面向对象的基础上,数据的表示方法和它们的相应操作封装在一个抽象数据类型或对象中。这种风格的组件是对象,或者说是抽象数据类型的实例。对象是一种被称作管理者(Manager)的组件,因为它负责保持资源的完整性(例如实现对属性,方法的封装)。对象是通过函数和过程调用来交互的。
图3:数据抽象和面向对象风格的体系结构
对象风格的体系结构具有以下的特点:
(1)对象抽象使得组件和组件之间的操作以黑箱的方式进行。
(2)封装性使得细节内容对外部环境得以良好的隐藏。对象之间的访问是通过方法调用来实现的。
(3)考虑操作和属性的关联性,封装完成了相关功能和属性的包装,并由对象来对它们进行管理。
(4)使用某个对象提供的服务并不需要知道服务内部是如何实现的。
数据抽象和面向对象风格的体系结构在现代的软件开发中广泛应用。但是,面向对象体系结构也存在着某些问题:
(1)对象之间的耦合度比较紧:为了使一个对象和另一个对象通过过程调用等进行交互,必须知道对象的标识。只要一个对象的标识改变了,就必须修改所有其他明确调用它的对象。
(2)必须修改所有显式调用它的其它对象,并消除由此带来的一些副作用。例如A使用了对象B,C也使用了对象B,那么,C对B的使用所造成的对A的影响可能是不可预测的。
分层风格的体系结构是将系统组织成一个层次结构,每一层为上层提供服务,并作为下层的客户端。在分层风格的体系结构中,一般内部的层只对相邻的层可见。层之间的连接器(conector)通过决定层间如何交互的协议来定义。
图4:分层风格的体系结构
这种风格支持基于可增加抽象层的设计。这样,允许将一个复杂问题分解成一个增量步骤序列的实现。由于每一层最多只影响两层,同时只要给相邻层提供相同的接口,允许每层用不同的方法实现,同样为软件复用提供了强大的支持。
分层风格的体系结构有许多可取的属性:
(1)支持基于抽象程度递增的系统设计:使设计者可以把一个复杂系统按递增的步骤进行分解;
(2)支持功能增强:因为每一层至多和相邻的上下层交互,因此功能的改变最多影响相邻的上下层;
(3)支持复用:只要提供的服务接口定义不变,同一层的不同实现可以交换使用。这样,就可以定义一组标准的接口,而允许各种不同的实现方法。
但是,分层风格的体系结构也有其不足之处:
(1)并不是每个系统都可以很容易地划分为分层风格的体系结构,甚至即使一个系统的逻辑结构是层次化的,出于对系统性能的考虑,系统设计师不得不把一些低级或高级的功能综合起来;
(2)很难找到一个合适的、正确的层次抽象方法。
总结一下调用/返回风格的软件体系结构:这类架构中的组件就是各种不同的操作单元(例如,子程序、对象、层次),而连接器则是这些对象之间的调用关系(例如,主-子程序调用,或者对象的方法以及层次体系结构中的协议)。调用-返回结构的优点在于,容易将大的架构分解为一种层次模型,在较高的层次,隐藏那些比较具体的细节,而在较低的层次,又能够表现出实现细节。在这类体系结构中,调用者和被调用者之间的关系往往比较紧密。在这样的情况下,架构的扩充通常需要被调用者和所有调用者都进行适当的修改。
◆虚拟机风格的体系结构【Virtual Machine Style】
虚拟机风格的体系结构设计的初衷主要是考虑体系结构的可移植性。这种体系结构力图模拟它运行于其上的软件或者硬件的功能。
图5:虚拟机风格的体系结构
这种体系结构在很多场合都有用途:
n 它可以被用于模拟或者测试尚未构建成的软硬件系统,它还可以模拟灾难,(例如飞行模拟器,安全攸关的系统 ,这些在现实生活中测试太过于危险并且牺牲太大。
n 虚拟机的例子有:解释器,机遇规则的系统,通用语言处理程序。
我们用解释器作一个例子,通过虚拟机特定模块的解释步骤如下所示:
n 解释引擎从被解释的模块中选择一条指令;
n 基于这条指令,引擎更新虚拟机内部的状态;
n 上述过程反复执行;
由于对程序的中断,查询和在运行时的修改能力,通过虚拟机来执行模块大大提高了其稳定性,但是由于在执行过程中附加了额外的计算量,性能方面必然会受到影响。
在虚拟机结构中,组件是被模拟的机器和实际的机器,而连接器则是一组转换规则,这组转换规则能够在保持被模拟的机器中的虚拟状态和逻辑不变的前提下,将操作转换为实际的机器中能够被理解的操作。
这类架构的突出特点是:
n 在虚拟机器环境中运行的代码不必须了解虚拟机的具体细节。
n 一旦运行环境发生变化,只需要重写虚拟机本身,而不是整个系统。
n 通常虚拟机会限制在其中运行的软件的行为,特别是那些以实现跨平台为目的的虚拟机,如Java虚拟机和.NET CLR。这类虚拟机往往希望虚拟机器的代码完全不了解虚拟机以外的现实世界。这是在灵活性、效率与软件跨平台性之间进行的一种折衷。
n 能够使系统的结构更具层次性,使用虚拟机提供的设施编写的代码,可以不考虑虚 拟机以外的实际环境,而在正确地实现了这种虚拟机的环境中执行。
虚拟机架构的缺点是灵活性略显不足,无法最大限度地发挥操作系统和硬件的性能(在
虚拟机中运行的应用程序并不了解实际的硬件和操作系统的特性。
◆独立组件风格的体系结构【Independent Components Style】
独立组件风格的体系结构由很多独立的通过消息交互的过程或者对象组成。
图6:独立组件风格的体系结构
这种软件体系结构通过对各自部分计算的解耦操作来达到易更改的目的。它们之间相互的传输数据,但是不直接控制双方。消息可能传递给:
· 指定的参与项