【Java自学】二、理解面向对象

  Java 语言是纯粹的面向对象的程序设计语言,这主要表现为Java 完全支持面向对象的三种基本特征:继承、封装和多态。Java 语言完全以对象为中心, Java 程序的最小程序单位是类, 整个Java 程序由一个一个的类组成。
  Java 完全支持使用对象、类、继承、封装、消息等基本概念来进行程序设计,允许从现实世界中客观存在的事物(即对象)出发来构造软件系统,在系统构造中尽可能运用人类的自然思维方式。实际上,这些优势是所有面向对象编程语言的共同特征。面向对象的方式实际上由OOA (面向对象分析)、OOD(面向对象设计)和OOP (面向对象编程) 三个部分有机组成,其中, OOA 和OOD 的结构需要使用一种方式来描述并记录,目前业界统一采用UML (统一建模语言)来描述并记录OOA 和OOD 的结果。
  目前UML 的最新版本是2.0 ,它一共包括13 种类型的图形,使用这13 种图形中的某些就可以很好地描述并记录软件分析、设计的结果。通常而言, 没有必要为软件系统绘制13 种UML 图形, 常用的UML 图形有用例图、类图、组件图、部署图、顺序图、活动图和状态机图。本章将会介绍UML 图的相关概念,也会详细介绍这7种常用的UML 图的绘制方法。

2.1 面向对象
  在目前的软件开发领域有两种主流的开发方法: 结构化开发方法和面向对象开发方法。早期的编程语言如C 、Basic 、Pascal 等都是结构化编程语言:随着软件开发技术的逐渐发展,人们发现面向对象可以提供更好的可重用性、可扩展性和可维护性,于是催生了大量的面向对象的编程语言, 如C++ 、Java 、C#和Ruby 等。
 2.1.1 结构化程序设计简介
  结构化程序设计方法主张按功能来分析系统需求,其主要原则可概括为自顶向下、逐步求精、模块化等。结构化程序设计首先采用结构化分析( Structured Analysis, SA ) 方法对系统进行需求分析,然后使用结构化设计( Structured Design, SD ) 方法对系统进行概要设计、详细设计, 最后采用结构化编程( Structured Program, SP) 方法来实现系统。使用这种SA 、SD 和SP 的方式可以较好地保证软件系统的开发进度和质量。
  因为结构化程序设计方法主张按功能把软件系统逐步细分,因此这种方法也被称为面向功能的程序设计方法: 结构化程序设计的每个功能都负责对数据进行一次处理,每个功能都接受一些数据,处理完后输出一些数据,这种处理方式也被称为面向数据流的处理方式。
  结构化程序设计里最小的程序单元是函数,每个函数都负责完成一个功能,用以接收一些输入数据,函数对这些输入数据进行处理,处理结束后输出一些数据。整个软件系统由一个个函数组成,其中作为程序入口的函数被称为主函数,主函数依次调用其他普通函数,普通函数之间依次调用,从而完成整个软件系统的功能。图2. 1 显示了结构化软件的逻辑结构示意图。

  从图2.1 可以看出,结构化设计需要采用自顶向下的设计方式,在设计阶段就需要考虑每个模块应该分解成哪些子模块,每个子模块又分解成哪些更小的模块..... .依此类推,直至将模块细化成一个个函数。

  每个函数都是具有输入、输出的子系统, 函数的输入数据包括函数形参、全局变量和常量等,函数的输出数据包括函数返回值以及传出参数等。结构化程序设计方式有如下两个局限性。

  • 设计不够直观,与人类习惯思维不一致。采用结构化程序分析、设计时,开发者需要将客观世界模型分解成一个个功能,每个功能用以完成一定的数据处理。
  • 适应性差,可扩展性不强。由于结构化设计采用自顶向下的设计方式,所以当用户的需求发生改变,或需要修改现有的实现方式时,都需要自顶向下地修改模块结构,这种方式的维护成本相当高。

2.1.2 程序的三种基本结构
  在过去的日子里,很多编程语言都提供了GOTO 语句, GOTO 语句非常灵活,可以让程序的控制流程任意流转一一如果大量使用GOTO 语句,程序完全不需要使用循环。但GOTO 语句实在太随意了,如果程序随意使用GOTO 语句,将会导致程序流程难以理解,并且容易出错。在实际软件开发过程中,更注重软件的可读性和可修改性,因此GOTO 语句逐渐被抛弃了。

  结构化程序设计非常强调实现某个功能的算法,而算法的实现过程是由一系列操作组成的, 这些操作之间的执行次序就是程序的控制结构。1996 年,计算机科学家Bohm 和Jacopini 证明了这样的事实:任何简单或复杂的算法都可以由顺序结构、选择结构和循环结构这三种基本结构组合而成。所以, 这三种结构就被称为程序设计的三种基本结构,也是结构化程序设计必须采用的结构。
1. 顺序结构
  顺序结构表示程序中的各操作是按照它们在源代码中的排列顺序依次执行的,其流程如图2.2 所示。

  图中的S l 和S2 表示两个处理步骤,这些处理步骤可以是一个非转移操作或多个非转移操作,甚至可以是空操作,也可以是三种基本结构中的任一结构。整个顺序结构只有一个入口点a 和一个出口点b 。这种结构的特点是: 程序从入口点a 处开始,按顺序执行所有操作, 直到出口点b 处,所以称为顺序结构。

2. 选择结构
  选择结构表示程序的处理需要根据某个特定的条件选择其中的一个分支执行。选择结构有单选择、双选择和多选择三种形式。双选择是典型的选择结构形式, 其流程如图2.3 所示,图中的Sl 和S2 与顺序结构中的说明相同。

 

 

 

  由图中可见,在结构的入口点a 处有一个判断条件,表示程序流程出现了两个可供选择的分支,如果判断条件为真则执行Sl 处理,否则执行S2 处理。值得注意的是,这两个分支中只能选择一个且必须选择一个执行,但不论选择了哪一个分支执行,最后流程都一定到达结构的出口点b 处。

  当S1 和S2 中的任意一个处理为空时,说明结构中只有一个可供选择的分支,如果判断条件为真,则执行S1 处理,否则直接执行到结构出口点b 处。也就是说,如果判断条件为假时,则什么也没执行,所以称为单选择结构,如图2 .4所示。

 

 

 

  多选择结构是指程序流程中遇到如图2. 5 所示的S1 、S2 、S3 、S4… 多个分支,程序执行方向根据判断条件来确定。如果条件l 为真,则执行S1 处理:如果条件l 为假,条件2 为真,则执行S2 处理;如果条件l 为假,条件2 为假,条件3 为真,则执行S3 处理……依此类推。从图2.5 中可以看出, Sn处理的n 值越大,则需要满足的条件越苛刻。

 

 

 

  对于图2.5 所示的多选择结构,不论选择了哪一个分支,最后流程都要到达同一个出口点b 处。如果所有分支的条件都不满足,则直接到达出口点b 处。有些程序语言不支持多选择结构,但所有的结构化程序设计语言都是支持的。

3. 循环结构
  循环结构表示程序反复执行某个或某些操作,直到某条件为假(或为真)时才停止循环。在循环结构中最主要的是: 在什么情况下执行循环?哪些操作需要重复执行?循环结构的基本形式有两种:当型循环和直到型循环,其流程如图2. 6 所示。图中带S 标识的矩形内的操作称为循环体,即循环入口点a 到循环出口点b 之间的处理步骤,这就是需要循环执行的部分。而在什么情况下执行循环则要根据条件判断。

 

 

 

  • 当型结构:先判断条件,当条件为真时执行循环体,并且在循环体结束时自动返回到循环入口处,再次判断循环条件:如果条件为假,则退出循环体到达流程出口处。因为是"当条件为真时执行循环",即先判断后执行,所以被称为当型循环。其流程如图2.6 (a) 所示。
  • 直到型循环: 从入口处直接执行循环体,循环体结束时判断条件,如果条件为真,则返回入口处继续执行循环体,直到条件为假时退出循环体到达流程出口处,是先执行后判断。因为是"直到条件为假时结束循环",所以被称为直到型循环。其流程如图2.6 (b ) 所示。

  同样,循环结构也只有一个入口点a 和一个出口点b ,循环终止是指流程执行到循环的出口点。图中所表示的S 处理可以是一个或多个操作,也可以是一个完整的结构或过程。

  通过三种基本控制结构可以看到,结构化程序设计中的任何结构都具有唯一的入口和唯一的出口,并且程序不会出现死循环。在程序的静态形式与动态执行流程之间具有良好的对应关系。之所以详细介绍这些程序结构,主要因为Java 语言的方法体内同样是由这三种程序结构组成的,换句话说,虽然Java 是面向对象的,但Java 的方法里则是一种结构化的程序流。

2.1.3 面向对象程序设计简介
  面向对象是一种更优秀的程序设计方法, 它的基本思想是使用类、对象、继承、封装、消息等基本概念进行程序设计。它从现实世界中客观存在的事物(即对象〉出发来构造软件系统,并在系统构造中尽可能运用人类的自然思维方式,强调直接以现实世界中的事物(即对象)为中心来思考,认识问题,并根据这些事物的本质特点,把它们抽象地表示为系统中的类,作为系统的基本构成单元(而不是用一些与现实世界中的事物相关比较远,并且没有对应关系的过程来构造系统) ,这使得软件系统的组件可以直接映像到客观世界,并保持客观世界中事物及其相互关系的本来面貌。

  采用面向对象方式开发的软件系统,其最小的程序单元是类,这些类可以生成系统中的多个对象,而这些对象则直接映像成客观世界的各种事物。采用面向对象方式开发的软件系统逻辑上的组成结构如图2. 7 所示。

  从图2.7 可以看出,面向对象的软件系统由多个类组成,类代表了客观世界中具有某种特征的一类事物,这类事物往往有一些内部的状态数据,比如人有身高、体重、年龄、爱好等各种状态数据当然程序没必要记录该事物所有的状态数据,程序只要记录业务关心的状态数据即可。
  面向对象的语言不仅使用类来封装一类事物的内部状态数据,这种状态数据就对应于图2.7 中的成员变量(由Field 翻译而来,有些资料将其直译为宇段;还有些资料将其翻译为属性,但这个说法非常不准确, Java 的属性指的是Property); 而且类会提供操作这些状态数据的方法,还会为这类事物的行为特征提供相应的实现,这种实现也是方法。因此可以得到如下基本等式:

成员变量(状态数据) +方法(行为) =类定义

  从这个等式来看,面向对象比面向过程的编程粒度要大: 面向对象的程序单位是类;而面向过程的程序单位是函数(相当于方法) , 因此面向对象比面向过程更简单、易用。

  从面向对象的眼光来看,开发者希望从自然的认识、使用角度来定义和使用类。也就是说,开发者希望直接对客观世界进行模拟:定义一个类,对应客观世界的哪种事物;业务需要关心这个事物的哪些状态,程序就为这些状态定义成员变量:业务需要关心这个事物的哪些行为,程序就为这些行为定义方法。
  不仅如此,面向对象程序设计与人类习惯的思维方法有较好的一致性,比如希望完成"猪八戒吃西瓜"这样一件事情。
  在面向过程的程序世界里,一切以函数为中心,函数最大,因此这件事情会用如下语句来表达:
    吃(猪八戒,西瓜) ;
  在面向对象的程序世界里, 一切以对象为中心,对象最大,因此这件事情会用如下语句来表达:
    猪八成. 吃(西瓜) ;
  对比两条语句不难发现,面向对象的语句更接近自然语言的语法:主语、谓语、宾语一目了然,十分直观,因此程序员更易理解。

2.1.4 面向对象的基本特征
  面向对象方法具有三个基本特征: 封装(Encapsulation) 、继承( lnheritance) 和多态( Polymorphism) ,其中封装指的是将对象的实现细节隐藏起来, 然后通过一些公用方法来暴露该对象的功能:继承是面向对象实现软件复用的重要手段,当子类继承父类后, 子类作为一种特殊的父类,将直接获得父类的属性和方法:多态指的是子类对象可以直接赋给父类变量,但运行时依然表现出子类的行为特征,这意味着同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。

  除此之外,抽象也是面向对象的重要部分,抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象井不打算了解全部问题,而只是考虑部分问题。例如,需要考察Person 对象时,不可能在程序中把Person 的所有细节都定义出来,通常只能定义Person 的部分数据、部分行为特征一一而这些数据、行为特征是软件系统所关心的部分。

   面向对象还支持如下几个功能。

  • 对象是面向对象方法中最基本的概念,它的基本特点有: 标识唯一性、分类性、多态性、封装性、模块独立性好。
  • 类是具有共同属性、共同方法的一类事物。类是对象的抽象;对象则是类的实例。而类是整个软件系统最小的程序单元,类的封装性将各种信息细节隐藏起来,并通过公用方法来暴露该类对外所提供的功能,从而提高了类的内聚性,降低了对象之间的稿合性。
  • 对象间的这种相互合作需要一个机制协助进行,这样的机制称为"消息"。消息是一个实例与另一个实例之间相互通信的机制。
  • 在面向对象方法中,类之间共享属性和操作的机制称为继承。继承具有传递性。继承可分为单继承( 一个继承只允许有一个直接父类,即类等级为树形结构)与多继承( 一个类允许有多个直接父类) 。

  在编程语言领域,还有一个"基于对象"的概念, 这两个概念极易混淆。通常而言,"基于对象"也使用了对象,但是无法利用现有的对象模板产生新的对象类型,继而产生新的对象, 也就是说,"基于对象"没有继承的特点:而"多态"则更需要继承,没有了继承的概念也就无从谈论"多态"。面向对象方法的三大基本特征(封装、继承、多态〉缺一不可。例如, JavaScript 语言就是基于对象的,它使用一些封装好的对象,调用对象的方法,设置对象的属性:但是它们无法让开发者派生新的类,开发者只能使用现有对象的方法和属性。
  判断一门语言是否是面向对象的,通常可以使用继承和多态来加以判断。"面向对象"和"基于对象"都实现了"封装"的概念,但是面向对象实现了"继承和多态",而"基于对象"没有实现这些。
  面向对象编程的程序员按照分工分为"类库的创建者"和"类库的使用者"。使用类库的人并不都是具备了面向对象思想的人,通常知道如何继承和派生新对象就可以使用类库了,然而他们的思维并没有真正地转过来,使用类库只是在形式上是面向对象的,而实质上只是库函数的一种扩展。

2.2 UML (统一建模语言)介绍
  面向对象软件开发需要经过OOA (面向对象分析)、OOD (面向对象设计)和OOP (面向对象编程) 三个阶段, OOA 对目标系统进行分析, 建立分析模型,并将之文档化; OOD 用面向对象的思想对OOA 的结果进行细化,得出设计模型。OOA 和OOD 的分析、设计结果需要统一的符号来描述、交流并记录, UML 就是这种用于描述、记录OOA 和OOD 结果的符号表示法。

  面向对象的分析与设计方法在20 世纪80 年代末至90 年代中出现了一个高潮, UML 是这个高潮的产物。在此期间出现了三种具有代表性的表示方法。
  Booch 是面向对象方法最早的倡导者之一, 他提出了面向对象软件工程的概念。Booch 1993 表示法(由Booch 提出) 比较适合于系统的设计和构造。
  Rumbaugh 等人提出了面向对象的建模技术( OMT ) 方法, 采用面向对象的概念, 并引入了各种独立于语言的表示符。这种方法用对象模型、动态模型、功能模型和用例模型共同完成对整个系统的建模,所定义的概念和符号可用于软件开发的分析、设计和实现的全过程, 软件开发人员不必在开发过程的不同阶段进行概念和符号的转换。OMT-2 特别适用于分析和描述以数据为中心的信息系统。
  Jacobson 于1994 年提出了OOSE 方法,其最大特点是面向用例(Use-Case ) , 并在用例的描述中引入了外部角色的概念。用例的概念是精确描述需求的重要武器, 但用例贯穿于整个开发过程, 包括对系统的测试和验证。OOSE 比较适合支持商业工程和需求分析。

  UML 统一了Booch 、Rumbaugh 和Jacobson 的表示方法,而且对其进行了进一步的发展,并最终统一为大众所接受的标准建模语言。UML 是一种定义良好、易于表达、功能强大且普遍适用的建模语言, 它的作用域不限于支持面向对象的分析与设计, 还支持从需求分析开始的软件开发全过程。
  截至1996 年10 月, UML 获得了工业界、科技界和应用界的广泛支持, 己有700 多家公司表示支持采用UML 作为建模语言。1996 年年底, UML 己稳占面向对象技术市场的85% , 成为可视化建模语言事实上的工业标准。1997 年年底, OMG 组织C Object Management Group , 对象管理组织)采纳UML1.1 作为基于面向对象技术的标准建模语言。UML 代表了面向对象方法的软件开发技术的发展方向, 目前UML 的最新版本是2.0 , UML 的大致发展过程如图2 . 8 所示。

  图2 . 8 中的UML 1. 1 和UML 2.0 是UML 历史上两个具有里程碑意义的版本, 其中, UML 1.1 是OMG 正式发布的第一个标准版本, 而UML2.0 是目前最成熟、稳定的UML 版本。

  UML 图大致上可分为静态图和动态图两种, UML2 . 0 的组成如图2 . 9 所示。

 

   从图2.9 可以看出, UML2.0 一共包括13 种正式图形: 活动图( activity diagram ) 、类图( class diagram ) 、通信图( communication diagram , 对应于UML l. x 中的协作图) 、组件图(component diagram ) , 复合结构图( composite structure diagram, UML 2.0 新增〉、部署图(deployment diagram ) 、交互概观图( interactive overview diagram, UML 2.0 新增) 、对象图( object diagram ) 、包图( package diagram ) 、顺序图( sequence diagram ) 、状态机图( state machine diagram ) .、定时图( timing diagram, UML 2.0 新增) 、用例图( use case diagram ) 。

  当读者看到这13 种UML 图形时,可能会对UML 产生恐惧的感觉,实际上正如大家所想:很少有一个软件系统在分析、设计阶段对每个细节都使用13 种图形来表现。永远记住一点:不要把UML 表示法当成一种负担, 而应该把它当成一种工具, 一种用于描述、记录软件分析设计的工具。最常用的UML 图包括用例图、类图、组件图、部署图、)1/页序图、活动图和状态机图等。

 2.2.1 用例图

  用例图用于描述系统提供的系列功能, 而每个用例则代表系统的一个功能模块。用例图的主要目的是帮助开发团队以一种可视化的方式理解系统的需求功能,用例图对系统的实现不做任何说明,仅仅是系统功能的描述。
  用例图包括用例(以一个椭圆表示, 用例的名称放在椭圆的中心或椭圆下面)、角色(Actor , 也就是与系统交互的其他实体, 以一个人形符号表示)、角色和用例之间的关系(以简单的线段来表示) ,以及系统内用例之间的关系。用例图一般表示出用例的组织关系一一要么是整个系统的全部用例,要么是完成具体功能的一组用例。如下图所示:一个简单BBS的用例图:

  用例图通常用于表达系统或者系统范畴的高级功能。如上图所示,可以很容易看出该系统所提供的功能。这个系统允许注册用户登录、发帖和回复,其中发帖和回复需要依赖于登录: 允许管理员删除其他人的帖子,删帖也需要依赖于登录。
  用例图主要在需求分析阶段使用,主要用于描述系统实现的功能,方便与客户交流,保证系统需求的无二性,用实例图表示系统外观,不要指望用例图和系统的各个类之间有任何联系。不要把用例做得过多,过多的用例将导致难以阅读,难以理解:尽可能多地使用文字说明。

2.2.2 类图
  类图是最古老、功能最丰富、使用最广泛的UML 图。类图表示系统中应该包含哪些实体,各实体之间如何关联: 换句话说,它显示了系统的静态结构,类图可用于表示逻辑类,逻辑类通常就是业务人员所谈及的事物种类。
  类在类图上使用包含三个部分的矩形来描述,最上面的部分显示类的名称,中间部分包含类的属性,最下面的部分包含类的方法。

 

   类图除可以表示实体的静态内部结构之外,还可以表示实体之间的相互关系。类之间有三种基本关系:

  • 关联(包括聚合、组合〉
  • 泛化(与继承同一个概念〉
  • 依赖

1. 关联
  客观世界中的两个实体之间总是存在千丝万缕的关系,当把这两个实体抽象到软件系统中时,两个类之间必然存在关联关系。关联具有一定的方向性:如果仅能从一个类单方向地访问另一个类,则被称为单向关联;如果两个类可以互相访问对象,则被称为双向关联。一个对象能访问关联对象的数目被称为多重性,例如,建立学生和老师之间的单向关联,则可以从学生访问老师,但从老师不能访问学生。
  关联使用一条实线来表示,带箭头的实线表示单向关联。
  在很多时候,关联和属性很像,关联和属性的关键区别在于:类里的某个属性引用到另外一个实体时,则变成了关联。
  关联关系包括两种特例: 聚合和组合,它们都有部分和整体的关系,但通常认为组合比聚合更加严格。当某个实体聚合成另一个实体时,该实体还可以同时是另一个实体的部分,例如,学生既可以是篮球俱乐部的成员,也可以是书法俱乐部的成员; 当某个实体组合成另一个实体时,该实体则不能同时是一个实体的部分。聚合使用带空心菱形框的实线表示,组合则使用带实心菱形框的实线表示。图2. 12显示了几个类之间的关联关系。

 

  图2.12 中描述Teacher 和Student 之间的关联关系: 它们是双向关联关系,而且使用了多重性来表示Teacher 和Student 之间存在1 : N 的关联关系( 1..*表示可以是一个到多个),即一个Teacher 实体可以有1 个或多个关联的Student 实体; Student 和BasketB allClub 存在聚合关系,即l 个或多个Student 实体可以聚合成一个BasketBallClub实体;而Arm (手臂)和Student 之间存在组合关系, 2 个Arm 实体组合成一个Student 实体。

2. 泛化

泛化与继承是同一个概念,都是指子类是一种特殊的父类,类与类之间的继承关系是非常普遍的,继承关系使用带空心三角形的实线表示。图2 .1 3 显示了Student 和Person 类之间的继承关系。

 

   从图2.13 可以看出, Student 是Person 的子类,即Student 类是一种特殊的Person 类。

  还有一种与继承类似的关系,类实现接口可视为一种特殊的继承,这种实现用带空心画,三角形的虚线表示。

3. 依赖

  如果一个类的改动会导致另一个类的改动,则称两个类之间存在依赖。依赖关系使用带箭头的虚线表示, 其中箭头指向被依赖的实体。依赖的常见可能原因如下:

  • 改动的类将消息发给另一个类。
  • 改动的类以另一个类作为数据部分。
  • 改动的类以另一个类作为操作参数。

  通常而言,依赖是单向的,尤其是当数据表现和数据模型分开设计时,数据表现依赖于数据模型。例如, JDK 基础类库中的JTable 和DefaultTableModel , 关于这两个类的介绍请参考本书12.11 节的介绍。图2.14 显示了它们之间的依赖关系。

  对于图2.14 中表述的JTable 和DefaultTableModel 两个类,其中DefaultTableModel 是JTable 的数据模型, 当DefaultTableModel发生改变时, JTable 将相应地发生改变。

 

2.2.3 组件图
  对于现代的大型应用程序而言,通常不只是单独一个类或单独一组类所能完成的,通常会由一个或多个可部署的组件组成。对Java 程序而言,可复用的组件通常打包成一个JAR 、WAR 等文件:对C/C++应用而言,可复用的组件通常是一个函数库,或者是一个DLL (动态链接库)文件。
  组件图提供系统的物理视图,它的用途是显示系统中的软件对其他软件组件(例如,库函数)的依赖关系。组件图可以在一个非常高的层次上显示,仅显示系统中粗粒度的组件,也可以在组件包层次上显示。
  组件图通常包含组件、接口和Port 等图元, UML 使用带符号的矩形来表示组件,使用圆圈代表接口,使用位于组件边界上的小矩形代表Port 。

  组件的接口表示它能对外提供的服务规范,这个接口通常有两种表现形式。

  • 用一条实线连接到组件边界的圆圈表示。
  • 使用位于组件内部的圆圈表示。

  组件除可以对外提供服务接口之外,组件还可能依赖于某个接口,组件依赖于某个接口使用一条带半圆的实线来表示。图2 .15 显示了组件的接口和组件依赖的接口。

 

 

  图2.15 显示了一个简单的Order 组件,该组件对外提供一个Payable 接口,该组件需要依赖于一个CustomerLookup 接口一一通常这个CustomerLookup 接口也是系统中己有的接口。

  图2.16 显示了包含组件关系的组件图。

 

 

  从图2.16 可以看出,本系统绘制电子购物平台的几个核心组件,其中Order 组件提供OrderQuery接口,该接口允许Dispatch 组件查询系统中的订单及其状态, Order组件又需要依赖于Customer 组件的CustomerLookup 接口,通过该接口查询系统中的顾客信息; Order 组件也需要依赖于Inventory 组件的ProductQuery 接口,通过该接口查询系统中的产品信息。

 2.2.4 部署图

  现代的软件工程早己超出早期的单机程序,整个软件系统可能是跨国家、跨地区的分布式软件,软件的不同部分可能需要部署在不同地方、不同平台之上。部署图用于描述软件系统如何部署到硬件环境中,它的用途是显示软件系统不同的组件将在何处物理运行,以及它们将如何彼此通信。
  因为部署图是对物理运行情况进行建模, 所以系统的生产人员就可以很好地利用这种图来安装、部署软件系统。
  部署图中的符号包括组件图中所使用的符号元素,另外还增加了节点的概念:节点是各种计算资源的通用名称,主要包括处理器和设备两种类型,两者的区别是处理器能够执行程序的硬件构件(如计算机主机) ,而设备是一种不具备计算能力的硬件构件(如打印机) 。 UML 中使用三维立方体来表示节点,节点的名称位于立方体的顶部。图2 . 17 显示了一个简单的部署图。

  从图2.17 可以看出,整个应用分为5 个组件: Student 、Administrator、应用持久层、Student 数据库和UI 界面组件, 部署图准确地表现了各组件之间的依赖关系。除此之外, 部署图的重点在物理节点上, 图2.17 反映该应用需要部署在4 个物理节点上, 其中普通客户端无须部署任何组件,直接使用客户端浏览器即可; 管理者客户机上需要部署UI 界面; 应用服务器上需要部署Student 、Administrator 和应用持久层三个组件:而数据库服务器上需要部署Student 数据库。
2.2.5 顺序图

  顺序图显示具体用例〈或者是用例的一部分〉的详细流程,并且显示流程中不同对象之间的调用关系,同时还可以很详细地显示对不同对象的不同调用。顺序图描述了对象之间的交互(顺序图和通信图都被称为交互图) ,重点在于描述消息及其时间顺序。
  顺序图有两个维度:垂直维度,以发生的时间顺序显示消息/调用的序列;水平维度,显示消息被发送到的对象实例。顺序图的关键在于对象之间的消息,对象之间的信息传递就是所谓的消息发送,消息通常表现为对象调用另一个对象的方法或方法的返回值,发送者和接收者之间的箭头表示消息。
  顺序图的绘制非常简单。顺序图的顶部每个框表示每个类的实例(对象) , 框中的类实例名称和类名称之间用冒号或空格来分隔,例如myReportGenerator : ReportGenerator 。如果某个类实例向另一个类实例发送一条消息,则绘制一条指向接收类实例的带箭头的连线, 并把消息/方法的名称放在连线上面。
  对于某些特别重要的消息,还可以绘制一条带箭头的指向发起类实例的虚线, 将返回值标注在虚线上, 绘制带返回值的信息可以使得序列图更易于阅读。图2 .1 8 显示了用户登录顺序图。

 

 

 

  当绘制顺序图时,消息可以向两个方向扩展,消息穿梭在顺序图中,通常应该把消息发送者与接收者相邻摆放,尽量避免消息跨越多个对象。对象的激活期不是其存在的时间,而是它占据CPU 的执行时间,绘制顺序图时,激活期要精确。
  阅读顺序图也非常简单,通常从最上面的消息开始(也就是时间上最先开始的消息) ,然后沿消息方向依次阅读。
  在大多数情况下,交互图中的参与者是对象,所以也可以直接在方框中放置对象名, UML l .x 要求对象名有下画线; 2 .0 不再需要。
  绘制顺序图主要是帮助开发者对某个用例的内部执行清晰化,当需要考察某个用例内部若干对象行为时,应使用顺序图,顺序图擅长表现对象之间的协作顺序,不擅长表现行为的精确定义。

  与顺序图类似的还有通信图(以前也被称为协作图),通信图同样可以准确地描述对象之间的文互关系,但通信图没有精确的时间概念。一般来说,通信图可以描述的内容,顺序图都可以描述,但顺序图比通信图多了时间概念。

2.2.6 活动图

  活动图和状态机图都被称为演化图,其区别和联系如下。

  • 活动图:用于描述用例内部的活动或方法的流程,如果除去活动图中的并行活动描述,它就变成流程图。
  • 状态机图: 描述某一对象生命周期中需要关注的不同状态,并会详细描述剌激对象状态改变的事件,以及对象状态改变时所采取的动作。

演化图的5 要素如下。

  • 状态: 状态是对象响应事件前后的不同面貌,状态是某个时间段对象所保持的稳定态,目前的软件计算都是基于稳定态的,对象的稳定态是对象的固有特征,一个对象的状态一般是有限的。有限状态的对象是容易计算的,对象的状态越多,对象的状态迁移越复杂,对象状态可以想象成对象演化过程中的快照。
  • 事件: 来自对象外界的剌激,通常的形式是消息的传递,只是相对对象而言发生了事件。事件是对象状态发生改变的原动力。
  • 动作: 动作是对象针对所发生事件所做的处理,实际上通常表现为某个方法被执行。
  • 活动: 活动是动作激发的后续系统行为。
  • 条件: 条件指事件发生所需要具备的条件。

  对于激发对象状态改变的事件,通常有如下两种类型。

    • 内部事件:从系统内部激发的事件, 一个对象的方法( 动作)调用(通过事件激活)另一个对象的方法(动作) 。
    外部事件: 从系统边界外激发的事件,例如用户的鼠标、键盘动作。

  活动图主要用于描述过程原理、业务逻辑以及工作流技术。活动图非常类似于传统的流程图, 它也使用圆角矩形表示活动,使用带箭头的实线表示事件: 区别是活动图支持并发。图2 .19 显示了简单的活动图。

 

  从图2.19 可以看出,如果将这个活动图的两支分开, 每支就是一个传统的流程图,每个活动依次向下,遇到条件分支使用菱形框来表示条件。与传统的流程图不同的是, 活动图可以使用并行分支分出多条并行活动。
  绘制活动图时以活动为中心, 整个活动图只有一个开始活动,可以有多个结束活动。活动图需要将并行活动和串行活动分离,遇到分支和循环时最好像传统的流程图那样将分支、循环条件明确表示。活动图最大优点在于支持并行行为,并行对于工作流建模和过程建模非常重要。所以有了并行, 因此需要进行同步,同步通过汇合来指明。

 2.2.7 状态机图

  状态机图表示某个对象所处的不同状态和该类的状态转换信息。实际上, 通常只对"感兴趣的"对象绘制状态机图。也就是说, 在系统活动期间具有三个或更多潜在状态的对象才需要考虑使用状态机图进行描述。
  状态机图的符号集包括5个基本元素。

  • 初始状态,使用实心圆来绘制。
  • 状态之间的转换,使用具有带箭头的线段来绘制。
  • 状态,使用圆角矩形来绘制。
  • 判断点,使用空心圆来绘制。
  • 一个或者多个终止点,使用内部包含实心圆的圆来绘制。

  要绘制状态机图, 首先绘制起点和一条指向该类的初始状态的转换钱段。状态本身可以在图中的任意位置绘制,然后使用状态转换线段将它们连接起来。图2.20 显示了Hibemate 实体的状态机图。
  图2. 20 描绘了Hibemate 实体具有三个状态:瞬态、持久化和脱管。当程序通过new 直接创建一个对象时,该对象处于瞬态;对一个瞬态的对象执行save()、saveOrUpdate()方法后该对象将会变成持久化状态:对一个持久化状态的实体执行delete()方法后该对象将变成瞬态:持久化状态和脱管状态也可以相互转换。

 

 

  绘制状态机图时应该保证对象只有一个初始状态,可以有多个终结状态。状态要表示对象的关键快照,有重要的实际意义,无关紧要的状态则无须考虑,绘制状态机图时事件和方法要明确。
  状态机图擅长表现单个对象的跨用例行为,对于多个对象的交互行为应该考虑采用顺序图,不要对系统的每个对象都画状态机图,只对真正需要关心各个状态的对象才绘制状态机图。
2.3 Java的面向对象特征

  Java 是纯粹的面向对象编程语言,完全支持面向对象的三大基本特征:封装、继承和多态。Java程序的组成单位就是类,不管多大的Java 应用程序,都是由一个个类组成的。
2.3.1 一切都是对象
  在Java 语言中,除8 个基本数据类型值之外, 一切都是对象,而对象就是面向对象程序设计的中心。对象是人们要进行研究的任何事物,从最简单的整数到复杂的飞机等均可看作对象,它不仅能表示具体的事物,还能表示抽象的规则、计划或事件。
  对象具有状态, 一个对象用数据值来描述它的状态。Java 通过为对象定义成员变量来描述对象的状态: 对象还有操作,这些操作可以改变对象的状态,对象的操作也被称为对象的行为, Java 通过为对象定义方法来描述对象的行为。
  对象实现了数据和操作的结合,对象把数据和对数据的操作封装成一个有机的整体,因此面向对象提供了更大的编程粒度,对程序员来说,更易于掌握和使用。

  对象是Java 程序的核心,所以Java 里的对象具有唯一性,每个对象都有一个标识来引用它,如果某个对象失去了标识,这个对象将变成垃圾,只能等着系统垃圾回收机制来回收它。Java 语言不允许直接访问对象,而是通过对对象的引用来操作对象。
2.3.2 类和对象
  具有相同或相似性质的一组对象的抽象就是类,类是对一类事物的描述,是抽象的、概念上的定义;对象是实际存在的该类事物的个体,因而也称为实例( instance ) 。
  对象的抽象化是类,类的具体化就是对象,也可以说类的实例是对象。类用来描述一系列对象,类概述每个对象应包括的数据,类概述每个对象的行为特征。因此,可以把类理解成某种概念、定义,它规定了某类对象所共同具有的数据和行为特征。

Java 语言使用class 关键宇定义类,定义类时可使用成员变量来描述该类对象的数据,可使用方法来描述该类对象的行为特征。
在客观世界中有若干类,这些类之间有一定的结构关系。通常有如下两种主要的结构关系。

  • 一般→特殊关系: 这种关系就是典型的继承关系, Java 语言使用extends 关键字来表示这种继承关系, Java 的子类是一种特殊的父类。因此,这种一般→特殊的关系其实是一种" is a" 关系。
  • 整体→ 部分结构关系: 也被称为组装结构,这是典型的组合关系, Java 语言通过在一个类里保存另一个对象的引用来实现这种组合关系。因此,这种整体→ 部分结构关系其实是一种" has a"关系。

  开发者定义了Java 类之后,就可以使用new 关键字来创建指定类的对象了,每个类可以创建任意多个对象,多个对象的成员变量值可以不同一一这表现为不同对象的数据存在差异。

posted @ 2019-11-18 14:44  Peter.Luo  阅读(377)  评论(0)    收藏  举报