第7章 建立设计模型 设计模型是对分析模型的细化,实际上,设计模型和分析模型之间并没有严格的界线。分析模型偏重于理解问题域,描述软件要做什么,而设计模型则偏重于理解解决方案,描述软件究竟要如何做;分析模型只对系统作高层次的抽象,不关心技术与实现底层的细节,而设计模型则需要得到更详细更接近于程序代码的设计方案;分析模型侧重于对象行为的描述,而设计模型则侧重于对象属性和方法的描述;分析模型只关注功能性需求,而设计模型还需要考虑非功能性需求。 2018年9月21日星期五
第7章 建立设计模型 本章在客户服务系统分析模型的基础上,对其作进一步的细化,建立设计模型。本章将简单介绍设计模式的选择与应用、设计类及设计类间的关系,并介绍活动图、状态图的基本概念及其应用。为了进一步细化设计模型,本章还将介绍设计模型顺序图、设计模型的分包、逻辑视图到构件视图的映射,并介绍使用建模工具Rational Rose由分析模型产生程序代码、数据库结构的方法,让读者了解设计模型的建模过程及建模相关知识。
第7章 建立设计模型 7.1 设计模式的选择与应用 7.2构建设计类 7.3 详细设计类 7.4 设计类间关系 7.5 活动图 7.6 状态图 7.7 设计模型顺序图 7.8 设计模型的分包 7.9 逻辑视图到构件视图的映射 2018年9月21日星期五
7.1 设计模式的选择与应用 1. 问题引入 软件设计最重要的目标,一是要达到客户对系统功能和性能的要求;二是要考虑软件的生命周期,增强系统的可维护性,降低软件的维护费用。软件维护费用在软件开发成本中占有相当大的比例,一个软件项目能否盈利最关键的是看该软件的维护费用的高低。如果一个软件的可维护性较差,即可扩展性不强、可修改性差、可替换性不好,就会在该软件上花费太大的维护成本,甚至由于改动太大而将整个系统推翻重做。引入设计模式的目的就是要达到第二个目标,即增强系统的可维护性。然而,设计模式一般不能提高软件的功能和性能。那么什么是“软件设计模式”呢? 2018年9月21日星期五
7.1 设计模式的选择与应用 2. 解答问题 设计模式(Design Patterns)这个术语是在1990年代,由Erich Gamma等人,从建筑设计领域引入到计算机科学里去的。是对软件设计中普遍存在而又反复出现的各种问题,所提出的解决方案。设计模式并不直接用来完成程序代码的编写,而是描述在各种不同的情况下,要如何解决问题的一种方案。设计模式主要是使不稳定的依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。 2018年9月21日星期五
7.1 设计模式的选择与应用 3. 分析问题 在进一步了解设计模式之前,让我们先来了解模式的概念。 模式(Patterns)这个词,来自于Christopher Alexander的The Timeless Way of Building一书。Alexander在研究建筑结构的优质设计时,把模式定义为“在某一个情景下的问题解决方案”。他认为,每一种模式,都描述了在我们的环境中不断重复出现的问题,并描述了该问题解决方案的核心。有了模式,人们可以无数次地使用这种解决方式,以不变应万变,而不需要重新设计它。 2018年9月21日星期五
Alexander认为一个模式应有以下四个要素: 7.1 设计模式的选择与应用 Alexander认为一个模式应有以下四个要素: 模式的名称(Name of the Pattern) 模式的目的及解决的问题(Purpose of the Pattern, the Problem it Solves) 我们如何实现它(how to accomplish) 为了实现它我们必须考虑的限制和约束(constraints and forces we have to consider in order to accomplish it) 2018年9月21日星期五
7.1 设计模式的选择与应用 Alexander认为模式几乎可以解决可能遇到的所有建筑学问题。他还进一步认为模式可能结合在一起使用,以解决更复杂的建筑学问题。 当我们知道了模式的产生及基本内容之后,接下来看看“软件设计模式”是如何产生的,它能帮助我们解决什么样的问题?究竟有哪些设计模式?
7.1 设计模式的选择与应用 20世纪90年代前期,一些软件开发人员偶然接触了Alexander关于模式的著作。他们开始思考,适用于建筑学的模式对于软件开发是否也适用。在软件开发中重复出现的问题是否也能用同样的方式解决?一旦确立了某个模式,能否在新的设计中应用这个模式呢?很快地,这些人就感觉到,这两个问题的答案是肯定的。
7.1 设计模式的选择与应用 ① 复用解决方案 ② 建立通用的术语 ③ 解放视角 我们为什么要学习设计模式?它究竟能帮助我们解决什么问题? 设计模式至少可以让我们: ① 复用解决方案 利用已有的模式开发,可以借鉴他人的经验,减少开发成本和风险。 ② 建立通用的术语 在项目的分析和设计阶段,模式提供了约定俗成的词汇和视角,有利于团队内部的沟通。 ③ 解放视角 无论针对问题还是设计,设计模式都提供了高层次的视角,开发人员不必一开始就埋头于具体的细节之中。
7.1 设计模式的选择与应用 有哪些设计模式可供我们使用呢? 在“四人组”的《设计模式:可复用面向对象软件的基础》一书中收录了23个软件设计模式,如Facade模式、Adapter模式、Strategy模式、Factory模式、Bridge模式等。由于设计模式已超出了本书的范畴,因此,本节仅对其中的几个常用模式作一简单描述。如果您想全面学习设计模式,可参阅有关设计模式方面的书籍,如Alan Shalloway和James R. Trott所著的《Design Patterns Explained》、Eric Freeman和Elisabeth Freeman所著的《Head First Design Pattens》,都是非常好的设计模式学习资料。
7.1 设计模式的选择与应用 7.1.1 Facade(门面)模式 7.1.2 Adapter(适配器)模式 7.1.3 Factory(工厂)模式
7.1.1 Facade(门面)模式 1. 问题引入 当客户程序和组件中各种复杂的子系统之间有了太多的耦合,随着外部客户程序和各子系统不断演化,这种过多的耦合关系将使系统变得更加复杂而难以维护,因此,应用Facade模式,要求一个子系统的外部与其内部的通信时必须通过一个统一的Facade对象进行。Facade模式提供了一个高层次的接口,使得子系统更易于使用,并达到解耦合的目的。那么,Facade模式的原理是怎样的? 2018年9月21日星期五
Facade模式是对象的结构模式,它没有一个一般化的类图描述,图7-1显示了一个Facade模式的示意性对象图。 2. 解答问题 Facade模式是对象的结构模式,它没有一个一般化的类图描述,图7-1显示了一个Facade模式的示意性对象图。 2018年9月21日星期五
7.1.1 Facade(门面)模式 图7-1 Facade模式对象结构示意图
在这个对象图中,有两个角色: ① Facade(门面)角色 7.1.1 Facade(门面)模式 此角色知道相关的子系统(一个或者多个)的功能和职责。客户端可以调用这个角色的方法。在正常情况下,该角色会将所有从客户端发来的请求委派到相应的子系统中去。
7.1.1 Facade(门面)模式 ② 子系统角色 可以同时拥有一个或者多个子系统。每个子系统都不是一个单独的类,而是一个类的集合。每个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。
7.1.1 Facade(门面)模式 在门面模式中,通常只需要一个门面类,并且该门面类只有一个实例。但这并不意味着在整个系统里只能有一个门面类,一般地,每个子系统只有一个门面类。如果一个系统有多个子系统,每个子系统有一个门面类,整个系统可以有多个门面类。
7.1.1 Facade(门面)模式 3. 分析问题 门面模式是为子系统提供一个集中化和简单化的沟通管道,不能向子系统添加新的行为。 在以下情况下应用门面模式: (1) 希望包装或隐藏原有系统,提高原有系统的独立性。 (2) 希望使用原有系统的功能,并且希望增加一些新的功能。 (3) 为一个复杂的子系统提供一个简单接口。 (4) 在层次化结构中,可以使用Facade模式定义系统中每一层的入口。 2018年9月21日星期五
7.1.1 Facade(门面)模式 Facade模式的优点: (1) 它对客户端屏蔽了子系统组件,因而减少了客户端处理对象的数目,并使得子系统使用起来更加方便。 (2) 它实现了子系统与客户之间的松散耦合关系,而子系统内部的功能组件往往是紧耦合的。松散耦合关系使得子系统与客户的依赖关系减弱了,子系统的组件变化不会影响到它的客户,提高了系统的可维护性。 (3) 方便添加新功能。只需在Facade里添加新的方法,然后调用拥有新功能的类和方法就可以了,不必改变实际执行任务的类。 (4) 可以在不同系统间进行切换,只需要修改Facade类里所能调用的实际执行任务的类和方法。 2018年9月21日星期五
7.1.2 Adapter(适配器)模式 1. 问题引入 根据“四人组”的说法,Adapter模式的意图是将一个类的接口转换成客户希望的另外一个接口。Adapter模式是用于解决由于接口不兼容而不能一起工作的类的问题(这里的接口不是指Interface而是指类的公有方法)。使用Adapter模式后可以让这些不兼容的类一起工作。那么,Adapter模式的结构是怎样的? 2018年9月21日星期五
2. 解答问题 7.1.2 Adapter(适配器)模式 Adapter模式的结构,如图7-2所示。 2018年9月21日星期五
7.1.2 Adapter(适配器)模式 3. 分析问题 Adapter模式主要用来解决类不兼容问题,为了说明其结构,在此还是借用在理解多态性时常用的形状(Shape)实例。 假如有点、线、圆三种形状,分别为这三种形状创建类,命名为Point、Line、Circle,这些类都有“显示”的行为。客户对象只需要知道,它们拥有的是这些形状中的一个,不必知道自己真正拥有的对象是点、线还是圆。因此,定义一个形状(Shape)类作为超类,然后由它派生出Point、Line、Circle三个类。客户对象仅与Shape对象直接打交道,如图7-3所示。 2018年9月21日星期五
7.1.2 Adapter(适配器)模式 图7-3 客户对象与Shape、Point、Line、Circle对象之间的关系
7.1.2 Adapter(适配器)模式 现在假设每个点(Point)、线(Line)、圆(Circle)对象都具有一些行为,比如“设置位置”、“获取位置”、“设置颜色”、“获取颜色”、“绘制自己”、“擦除自己”等。前四项对于每种类型的形状来说其操作都是相同的,而对于后两项,不同类型的形状其操作略有不同。在此,使用多态来实现其接口问题,在Shape类(Shape类通常定义为抽象类)中为这些行为定义了接口(注:该处的接口不是interface,而是定义为public的方法。),然后在每个派生类中实现其相应的行为。其结构如图7-4所示。 2018年9月21日星期五
7.1.2 Adapter(适配器)模式 图7-4 包含操作的Point、Line、Circle类 2018年9月21日星期五
到此,可能你会问,这与Adapter模式有什么关系?的确还没有关系! 利用多态,客户对象只需告诉Point、Line或Circle对象要做一些事,每个Point、Line或Circle都会根据自己的类型做出相应的行为。 到此,可能你会问,这与Adapter模式有什么关系?的确还没有关系! 2018年9月21日星期五
7.1.2 Adapter(适配器)模式 假设现在客户要求我们实现一种新的形状(Shape)——矩形(Rectangle)。有两种方法可以完成这个任务,最直接的方法是:创建一个新的类——Rectangle类,来实现“矩形”这个“形状”,同样从Shape类派生出Rectangle类,这样我们仍然可以获得多态行为。但我们必须为Rectangle类编写paint()、erasure()这两个方法。这是一件比较费时费力的事。这样,我们可以采用另一种方法:找一个Rectangle类的替代品。它可能会很好地处理矩形的相关问题。但非常遗憾!我们找到的替代品可能不兼容,并且不能被修改。假设这个Rectangle类的替代品名为TrueRectangle,并且方法名也不是paint和erasure,而是display和undisplay。如图7-5所示。 2018年9月21日星期五
7.1.2 Adapter(适配器)模式 图7-5 包含不同方法名的TrueRectangle类 2018年9月21日星期五
7.1.2 Adapter(适配器)模式 怎么办呢?我们不能直接使用TrueRectangle类,因为那样就无法保持Shape类的多态行为。主要是因为: (1) 无法从Shape类直接派生出TrueRectangle类。要这样做的话,只能修改TrueRectangle类,将其超类改为Shape,但这是不被允许的,因为我们无权修改TrueRectangle类的代码(例如,TrueRectangle类已无源代码可修改)。 (2) TrueRectangle类中的方法名称和参数列表与Shape类的不同。 2018年9月21日星期五
7.1.2 Adapter(适配器)模式 要解决这种不兼容性,我们只能另想办法。 我们可以创建一个新类——Rectangle类,该类派生自Shape类。Rectangle类用来实现Shape接口而不必重写TrueRectangle类中矩形的实现代码。加入Rectangle类和TrueRectangle类后,其结构如图7-6所示。 2018年9月21日星期五
5.3.1 关联 图7-6 Rectangle类组合了TrueRectangle类 2018年9月21日星期五
7.1.2 Adapter(适配器)模式 由图中看出,Rectangle类派生自Shape类,Rectangle对象包含TrueRectangle对象,Rectangle对象将收到的消息转发给内部的TrueRectangle对象。Rectangle类与TrueRectangle类之间是组合关系,表示当一个Rectangle对象被实例化时,它必须实例化一个相应的TrueRectangle对象。Rectangle对象收到的任何请求都将被转发给TrueRectangle对象,由TrueRectangle对象完成实际的任务。这里,Rectangle类被称为Adapter(适配器),而TrueRectangle类则被称为Adaptee(源类)。这就是所谓的Adapter模式。 2018年9月21日星期五
7.1.2 Adapter(适配器)模式 适配器Retangle的Java程序代码如下所示。 class Rectangle extends Shape { …… private TrueRectangle tr; public Rectangle ( ) { tr = new TrueRectangle ( ); } void public paint ( ) { tr.display ( ); void public erasure ( ) { tr.undisplay ( );
7.1.2 Adapter(适配器)模式 实际上,Adapter模式有两种类型: (1) 对象Adapter模式 使用Adapter模式的意义在于复用(reuse)。使控制范围之外的一个原有对象与某个接口匹配,当现有接口与现实环境要求不一致的时候使用。
Adapter模式实际的应用场合主要有两个: 复用早期版本的程序代码; 系统需要使用已有的类,而此类的接口不符合系统的需要。 设计系统时考虑使用第三方组件。 建立一个可以重复使用的类,用于将第三方组件融入到当前系统中。
Facade模式与Adapter模式的比较: Facade模式的目的是简化接口,而Adapter模式的目的是将一个接口转换成另一个现有的接口。一个Facade背后隐藏了多个类,而一个Adapter只隐藏了一个类。
7.1.3 Factory(工厂)模式 假如有一个类A,当我们要实例化这个类时,最简单的方法就是A a = new A( )。如果要做一些初始化的工作,通常我们会把这些操作写在A的构造方法里,比如: A a = new A(parameter); 但是,也许有很多的初始化内容,如果把所有这些内容都放在构造方法里面,可能很不合适。在这种情形下可以使用工厂模式,协助完成一系列初始化工作。 工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态决定将哪一个类实例化,不必事先知道每次要实例化哪一个类。工厂模式有三种形态:Simple Factory(简单工厂)模式、Factory Method(工厂方法)模式和Abstract Factory(抽象工厂)模式。下面就以农产品管理系统的几种水果和蔬菜(苹果、葡萄、橙子、番茄、冬瓜、胡萝卜)为例简要介绍这三种形态的工厂模式。 2018年9月21日星期五
1. 问题引入 一、Simple Factory模式
一、Simple Factory模式 2. 解答问题 Simple Factory模式的结构如图7-7所示。 2018年9月21日星期五
一、Simple Factory模式 3. 分析问题 图7-7中,水果(Fruit)被定义为接口,并定义了两个方法:grow()和expressedJuice()。苹果(Apple)、葡萄(Grape)和橙子(Orange)三个具体类实现了水果接口。水果工厂类(FruitFactory)定义了一个方法trafficFruit(which:int):Fruit,用来决定究竟要运送哪一种水果。其实现过程可用如下Java程序代码表示。 2018年9月21日星期五
(1) 定义水果(Fruit)接口: 一、Simple Factory模式 public interface Fruit { public void grow(); //水果是被栽培的 public void expressedJuice(); //水果是可以榨汁的 }
一、Simple Factory模式 (2) 定义苹果(Apple)类实现水果(Fruit)接口: public class Apple implements Fruit { public void grow() { System.out.println("栽培苹果"); …… } public void expressedJuice() { System.out.println("榨苹果汁");
一、Simple Factory模式 (3) 定义葡萄(Grape)类实现水果(Fruit)接口: public class Grape implements Fruit { public void grow() { System.out.println("栽培葡萄"); …… } public void expressedJuice() { System.out.println("榨葡萄汁");
一、Simple Factory模式 (4) 定义橙子(Orange)类实现水果(Fruit)接口: public class Orange implements Fruit { public void grow() { System.out.println("栽培橙子"); …… } public void expressedJuice() { System.out.println("榨橙汁");
一、Simple Factory模式 (5) 定义水果工厂(FruitFactory)类: public class FruitFactory { public static Fruit trafficFruit(int which) { if (which == 1) { // 如果是1,则返回苹果实例 return new Apple(); } else if (which == 2) { // 如果是2,则返回葡萄实例 return new Grape(); else if (which == 3) { // 如果是3,则返回橙子实例 return new Orange(); else { return null;
一、Simple Factory模式 (6) 编写客户(Client)类: public class Client { public static void main(String args[]) { FruitFactory fruit = new FruitFactory(); //实例化水果工厂,可以开始运输水果了。 fruit.trafficFruit(3).expressedJuice(); //调用橙子的expressedJuice()方法,用于榨橙汁 }
一、Simple Factory模式 从上面的Java程序代码可以看出,当需要运送水果时,只需向水果工厂(FruitFactory)请求就可以了。水果工厂在接到请求后,会自行判断创建和提供哪一种水果。
二、Factory Method模式 1. 问题引入 对于上面的简单工厂模式来说,如果要增加一种或几种新的水果(比如还要增加梨、草莓等)就比较麻烦。这时除了要定义几个新增的水果类之外,还必须修改水果工厂和客户端。从而可以看出,Simple Factory(简单工厂)模式的开放性比较差。 如何来解决这种开放性比较差的问题呢?这就需要用到下面将要介绍的Factory Method(工厂方法)模式。然而,Factory Method模式的结构又是怎样的呢? 2018年9月21日星期五
二、Factory Method模式 2. 解答问题 将对象的创建交由父类中定义的一个标准方法来完成,而不是其构造方法,究竟应该创建何种对象由具体的子类负责。如图7-8所示。 2018年9月21日星期五
二、Factory Method模式 图7-8 Factory Method(工厂方法)模式 2018年9月21日星期五
3. 分析问题 二、Factory Method模式 对于Factory Method模式,其实现过程用Java程序代码表示如下: (1) 水果(Fruit)接口的定义,苹果(Apple)类、葡萄(Grape)类和橙子(Orange)类的定义与上面Simple Factory模式相同。 2018年9月21日星期五
(2) 我们将水果工厂定义为一个接口而不是一个具体类: 二、Factory Method模式 (2) 我们将水果工厂定义为一个接口而不是一个具体类: public interface FruitFactory { public Fruit trafficFruit(); //定义运送水果这一过程 }
(3) 定义运送苹果(TrafficApple)类实现水果工厂(FruitFactory)接口: 二、Factory Method模式 (3) 定义运送苹果(TrafficApple)类实现水果工厂(FruitFactory)接口: public class TrafficApple implements FruitFactory { public Fruit trafficFruit() { return new Apple(); //返回苹果实例 }
(4) 定义运送葡萄(TrafficGrape)类实现水果工厂(FruitFactory)接口: 二、Factory Method模式 (4) 定义运送葡萄(TrafficGrape)类实现水果工厂(FruitFactory)接口: public class TrafficGrape implements FruitFactory { public Fruit trafficFruit() { return new Grage(); //返回葡萄实例 }
(5) 定义运送橙子(TrafficOrange)类实现水果工厂(FruitFactory)接口: 二、Factory Method模式 (5) 定义运送橙子(TrafficOrange)类实现水果工厂(FruitFactory)接口: public class TrafficOrange implements FruitFactory { public Fruit trafficFruit() { return new Orange(); //返回橙子实例 }
二、Factory Method模式 (6) 定义客户(Client)类: public class Client { public static void main(String args[]) { TrafficOrange tarffic = new TrafficOrange(); //开始运送橙子 traffic.trafficFruit().expressedJuice(); //调用橙子的expressedJuice()方法,用于榨橙汁 }
二、Factory Method模式 从上面的Java程序代码可以看出,工厂方法模式的核心在于一个抽象工厂类(FruitFactory),它允许多个具体类从抽象工厂类中继承其创建的行为,从而可以成为多个简单工厂模式的综合,推广了简单工厂模式。同样地,如果需要在工厂方法模式中新增加一种水果(如梨子),那么只需要再定义一个新的水果类(如Pear)以及它所对应的工厂类(如TrafficPear)。不需要修改抽象工厂(FruitFactory)和其它已有的具体工厂(TrafficApple、TrafficGrape和TrafficOrange),也不需要修改客户端(Client)。
三、Abstract Factory模式 1. 问题引入 Factory Method模式针对的只是一种类别(如本例中的水果类Fruit),如果我们还要运送蔬菜,就不行了。在这种情况下,必须用到下面我们将要介绍的Abstract Factory(抽象工厂)模式。那么,Abstract Factory模式的结构又是如何的呢? 2018年9月21日星期五
三、Abstract Factory模式 2. 解答问题 2018年9月21日星期五
三、Abstract Factory模式 图7-9 Abstract Factory(抽象工厂)模式
对于Abstract Factory模式,其实现过程用Java程序代码表示如下: 3. 分析问题 对于Abstract Factory模式,其实现过程用Java程序代码表示如下: (1) 水果(Fruit)接口的定义,苹果(Apple)类、葡萄(Grape)类和橙子(Orange)类的定义与上面Simple Factory模式相同。 2018年9月21日星期五
(2) 定义一个蔬菜(Vegetable)接口: 三、Abstract Factory模式 (2) 定义一个蔬菜(Vegetable)接口: public interface Vegetable { public void grow(); //蔬菜是种植的 public void cook(); //蔬菜用来烹调 }
三、Abstract Factory模式 (3) 定义番茄(Tomato)类实现蔬菜(Vegetable)接口: public class Tomato implements Vegetable { public void grow() { System.out.println("种番茄"); …… } public void cook() { System.out.println("煮番茄");
三、Abstract Factory模式 (4) 定义冬瓜(WhiteGourd)类实现蔬菜(Vegetable)接口: public class WhiteGourd implements Vegetable { public void grow() { System.out.println("种冬瓜"); …… } public void cook() { System.out.println("煮冬瓜");
三、Abstract Factory模式 (5) 定义胡萝卜(Carrot)类实现蔬菜(Vegetable)接口: public class Carrot implements Vegetable { public void grow() { System.out.println("种胡萝卜"); …… } public void cook() { System.out.println("煮胡萝卜");
(6) 定义运送工厂(ITrafficFactory)接口: 三、Abstract Factory模式 (6) 定义运送工厂(ITrafficFactory)接口: public interface ITrafficFactory { public Fruit trafficFruit(Fruit whichFruit); //运送水果工厂方法 public Vegetable trafficVegetable(Vegetable whichVegetable); //运送蔬菜工厂方法 }
三、Abstract Factory模式 (7) 定义运送工厂(TrafficFactory)类实现运送工厂(ITrafficFactory)接口: public class TrafficFactory implements ITrafficFactory { //运送水果工厂方法 public Fruit trafficFruit(Fruit whichFruit) { return whichFruit; } //运送蔬菜工厂方法 public Vegetable trafficVegetable(Vegetable whichVegetable) { return whichVegetable;
三、Abstract Factory模式 (8) 编写客户(Client)类: public class Client { public static void main(String args[]) { Fruit orange = new Orange(); //橙子实例 Vegetable carrot = new Carrot(); //胡萝卜实例 TrafficFactory factory = new TrafficFactory(); //运送工厂实例 factory.trafficFruit(orange).expressedJuice(); //运送橙子,榨橙汁 factory.trafficVegetable(carrot).cook(); //运送胡萝卜,煮胡萝卜 }
Abstract Factory模式只需向客户端提供一个接口,使得客户端在不必指定运送农产品的具体类型的情况下,创建多个类型中的产品对象。
7.2 构建设计类 分析模型是逻辑上的解决方案,是设计模型的蓝本。设计模型是物理上实现解决方案的蓝本。在体系结构方面,分析模型面向问题领域,而设计模型面向实现环境。 分析模型中类与类之间的关系,是最顶层的关系,我们把在分析阶段得到的类称为“分析类”;然而要实现这些类,还必须在设计阶段做很多工作,比如增加接口、类、事件等以实现这些分析类,这个阶段的类被称为“设计类”。
7.2.1 从分析类生成设计类 1. 问题引入 分析模型中的所有类都是分析类。分析类展示的是高层次的属性和操作的集合,面向问题领域。它们表示最终的设计类可能具有的属性和操作。分析模型是设计模型的输入,设计模型是把实现技术加入分析模型后对分析模型的细化。如何从分析类生成设计类呢? 2018年9月21日星期五
7.2.1 从分析类生成设计类 2. 解答问题 分析类表示设计元素实例扮演的角色,可以使用一个或多个设计模型元素来实现这些角色。当然,单个设计元素也可以实现多个角色。如在分析阶段的“客户资料”类,在设计阶段分成了两个类,如图7-10所示。 2018年9月21日星期五
7.2.1 从分析类生成设计类 图7-10 从分析类生成设计类 2018年9月21日星期五
7.2.1 从分析类生成设计类 3. 分析问题 分析类Customer中包含了联系方式的一些属性,如果客户的联系方式改变了,或者一个客户不止一个联系方式,就会增加Customer类的维护难度,因此,在设计阶段必须把这些容易发生变化的部分抽取出来,封装成另一个类,以提高其重用性。联系方式(LinkMethod)类与客户(Customer)类之间是聚合关系,可以使得一个客户的联系方式也可以用在其它的场合。 2018年9月21日星期五
7.2.1 从分析类生成设计类 下面列出了设计类实现分析角色的一些基本方法: (1) 分析类可以成为设计模型中的单个设计类。 (2) 分析类可以成为设计模型中的设计类的一部分。 (3) 分析类可以成为设计模型中的聚集设计类。(表示不能将该聚集中的部件显式建模为分析类)。 (4) 分析类可以成为从设计模型中的相同类继承的一组设计类。 (5) 分析类可以成为设计模型中的一组功能相关的设计类。 (6) 分析类可以成为设计模型中的设计子系统。 2018年9月21日星期五
7.2.1 从分析类生成设计类 (7) 分析类可以成为设计子系统的部件,例如一个或多个接口以及它们的对应实施。 (8) 分析类可以成为设计模型中的关系。 (9) 分析类之间的关系可以成为设计模型中的设计类。 (10) 分析类主要处理功能需求并对来自“问题域”的对象建模;设计类在处理功能需求的同时还处理非功能需求并对来自“解决方案域”的对象建模。 (11) 可以使用分析类来表示“希望系统支持的对象”,而无需决定用硬件支持分析类的多少部分,用软件支持分析类的多少部分。因此,可以使用硬件实现部分分析类,而根本不在设计模型中对分析类进行建模。
7.2.2 确定类的大小 1. 问题引入 定义一个类时一个重要的方面,就是需要确定这个类的大小,这是决定类是否可以被方便地使用或重用的关键所在。太大或者太复杂的类难以维护和改变,但只要它所有的特征都建立在一个独立的重要抽象之上,就可以设计一个相对来说比较大一些的类。客户服务系统中的CustomerService(客户服务人员)类就是一个相对比较大但容易理解的类。如何确定这个类的大小呢? 2018年9月21日星期五
7.2.2 确定类的大小 2. 解答问题 如图7-11所示。该类具有可以处理的操作有:创建咨询记录、修改咨询记录、添加经验库等,这些操作都建立在客户服务人员这个独立的抽象模型之上。 2018年9月21日星期五
7.2.2 确定类的大小 图7-11 客户服务人员类的部分操作 2018年9月21日星期五
7.2.2 确定类的大小 3. 分析问题 一般地,一个类所包含的属性或操作最好不要超过20个,如果类所包含的属性或操作太多,应该考虑将这个类根据其特征及职责范围划分成几个更小的类。 2018年9月21日星期五
一个类的特征必须可以提供它所需要的所有功能。一个完善的类操作可以提供以下四个方面的功能: 7.2.2 确定类的大小 一个类的特征必须可以提供它所需要的所有功能。一个完善的类操作可以提供以下四个方面的功能: 2018年9月21日星期五
7.2.2 确定类的大小 (1) 实现功能 用例履行类职责的操作被称为实现操作。通常在分析模型时发现这些操作,在设计模型中保留这些操作。如客户服务人员类的deleteExperienceBase、createConsultation等操作。
7.2.2 确定类的大小 (2) 访问功能 面向对象的封装原则意味着要尽量减少对类属性的直接访问,但是,我们必须提供某些间接访问(读取或修改)这些属性的方法。在面向对象开发中,通常使用set和get操作对可读写属性进行访问,如客户服务人员类的getName和setName等操作。
7.2.2 确定类的大小 (3) 管理功能 管理功能不描述一个类要做的事情,它们只提供一个类所需要实现的基本功能,如构造和析构这样的操作。该例中的newCustomerService操作就是用来完成管理功能的,在面向对象的编程语言中,常用构造方法(或构造函数)来实现,如与类名相同的CustomerService构造方法。在Java中,由于有自动垃圾回收机制,所以可以没有析构操作。
7.2.2 确定类的大小 (4) 辅助功能 辅助功能是实现类操作的一部分,它们总是私有的。为了完成某些任务,它们往往被一个类的公共操作或受保护操作所调用。它们减少了对重要数据的直接访问,保证了数据的安全性,并且有助于类的封装。
7.3 详细设计类 7.3.1 设计公用类 7.3.2 设计类接口 7.3.3 设计属性和方法
7.3.1 设计公用类 1. 问题引入 一些公共算法通常以自由子程序或非成员函数的方式实现。如果将它们放在一个(或一些)已经存在的类中,就会降低这个类(或这些类)的内聚性。那么,在设计阶段要如何来处理这些公共算法呢? 2018年9月21日星期五
7.3.1 设计公用类 2. 解答问题 最好将这些公共算法封装成一个特定的类。这些用来包含非成员函数的特定的类被称为公用类。 2018年9月21日星期五
7.3.1 设计公用类 3. 分析问题 例如,Java的 Swing包中,窗体和对话框无居中属性,无法设置窗体或对话框相对于屏幕居中。如果在每一个窗体或对话框中都定义一个方法来使其居中,就会使得代码冗余,因此,通常将窗体或对话框居中的算法封装成一个公用类,供所有需要居中处理的窗体或对话框使用。 软件设计通常会考虑软件的生命周期,提高软件的可维护性。为了解决设计上的问题,最好应用软件设计模式。设计模式的相关知识详见7.1设计模式的选择与应用。 2018年9月21日星期五
7.3.2 设计类接口 1. 问题引入 类接口是指其它类可以通过它访问该类的方法。类接口通常被定义为公有的访问方法。当为类接口确定最佳设计时,需要决定是创建尽可能多的特性(属性)还是创建尽可能少的特性(属性)。也就是说是要创建一个单独但复杂的操作来处理所有复杂的行为,还是要创建使用一系列设计良好的简单操作,每一个操作完成一项任务? 2018年9月21日星期五
7.3.2 设计类接口 2. 解答问题 用一系列设计良好的简单操作对行为建模,比用复杂的操作更好一些。由于每一个简单操作都只完成比较少的任务,接口的内聚性较高,所以更容易理解,也更容易重用,如图7-12所示。 2018年9月21日星期五
7.3.2 设计类接口 图7-12 设计良好的简单操作的类 2018年9月21日星期五
7.3.2 设计类接口 3. 分析问题 用一个单独但复杂的操作来处理所有复杂的行为可以使类接口变得非常简单,但是这个操作的参数和实现部分会变得相当复杂,因此,这部分内容的可理解性会变得很差,降低了系统的可维护性。如图7-13所示,对整个用户信息的获取与设置只用了两个接口,每个接口实现了比较复杂的行为,这种接口的设计凸显出很大的弊病:目标不明确。因为可能所获得的某些用户信息并不会被使用。 2018年9月21日星期五
7.3.2 设计类接口 图7-13 具有简单接口但复杂操作的类 2018年9月21日星期五
7.3.2 设计类接口 但使用过多的简单操作会使类的接口变得很复杂、很难理解。因为可能存在几个操作协作完成某一项任务的情况,这样就降低了类接口的内聚性,增强了类接口之间的耦合性。 2018年9月21日星期五
7.3.2 设计类接口 结论:一个有着复杂操作的接口和有着太多操作的接口同样难于理解。所以,通常会在这两者之间进行权衡,可以通过尽可能地使类接口变得简洁来使类变得容易理解和操作。 2018年9月21日星期五
7.3.3 设计属性和操作 分析模型中已经描述了类的属性和操作,它们包含了类的大体状态和职责。但在建立分析模型时仅仅是为属性和操作命名,而没有详细的定义。在建立设计模型时,需要对类的属性和操作添加更多的详细信息。比如,需要为属性确定数据类型和初始值,还需要为操作添加参数和返回类型。
一、设计属性 1. 问题引入 我们在前面的分析模型中确定了类的属性名,如何确定属性的数据类型与初始值并使用建模工具Rational Rose描述出来呢? 2018年9月21日星期五
一、设计属性 2. 解答问题 属性的UML定义格式为: 可见性 属性名:数据类型 = [初始值] 以CustomerService(客户服务人员)类的属性设计为例,结果如图7-14所示。 2018年9月21日星期五
一、设计属性 图7-14 类的属性设计 2018年9月21日星期五
在图7-14中,给CustomerService类定义了8个属性,详细情况见表8-1所示。 一、设计属性 3. 分析问题 在图7-14中,给CustomerService类定义了8个属性,详细情况见表8-1所示。 2018年9月21日星期五
一、设计属性 表8-1 CustomerService类属性定义 2018年9月21日星期五 属性名 中文对照 数据类型 初始值 name 姓名 String "" sex 性别 "男" age 年龄 int 20 phone 联系电话 rank 职位 dept 部门 loginName 登录名 password 密码 2018年9月21日星期五
一、设计属性 在属性和操作的规格说明中,可以用“public”、“protected”和“private”来定义属性或操作的可见性。由于对象的封装性和信息隐藏原则,类属性可见性默认定义为“private”(私有的)。 2018年9月21日星期五
一、设计属性 属性名里通常不含有空格,所以,如果需要用多个单词来定义一个属性名,这些单词是紧挨着的。并且人们习惯于把第一个单词小写,其它单词首字母大写。例如,name和loginName等。一般使用名词为属性命名。 2018年9月21日星期五
一、设计属性 属性设计中,数据类型包括基本数据类型和非基本数据类型两种。基本数据类型代表了最简单、最原始的类型,通常与编程语言有关。例如,int(整型)、char(字符型)和boolean(布尔型)等就是Java中的基本数据类型。非基本数据类型也叫复合数据类型,由基本数据类型演变而来,它们负责表达更复杂的数据类型,比如,类和数组就是非基本数据类型。
一、设计属性 在设计属性时,还需要考虑属性的初始值。初始值有时也称为默认值,是描述属性时可选择的特征,它描述了在创建对象(实例化对象)时对象中这个属性的值,例如,CustomerService类的sex(性别)属性的初始值为“男”。不同的对象其属性值可能不相同,该值是可以改变的。
一、设计属性 并不是所有对象的某个属性都用同一个初始值。对某些属性来说,每一个拥有这个属性的对象需要的初始值是不同的。例如,来电咨询类有一个咨询时间属性,当创建一个来电咨询对象时,希望其咨询时间就是系统的当前时间,所以,来电咨询类的咨询时间属性的初始值被设置为“Now”。虽然每个来电咨询对象的这个属性初始值的类型相同,但不同对象之间的属性值却是不同的,该值由创建对象的时间来决定。
一、设计属性 CustomerService类的sex属性在Rational Rose中的设计过程: 选择【Open Specification…】菜单项,打开【Class Specification for CustomerService】对话框; 在对话框中选择【Attributes】选项页; 在下面列表框的空白处点击鼠标右键,在弹出的快捷菜单中选择【Insert】项; 在列表框中增加一项,将其名字改为“sex”并双击该项; 弹出【Class Attributes Specification for sex】对话框,分别在Type(数据类型)、Initial(初始值)中输入“String”、“男”。
一、设计属性 整个过程如图7-15~8-19所示。其它属性的设计过程与其相似。 图7-16 创建属性页 图7-15 类规范对话框
一、设计属性 图7-18 新建“sex”属性 图7-17 在列表框空白处右击鼠标 图7-19 设置sex属性的数据类型和初始值
二、设计操作 1. 问题引入 在项目的详细设计阶段除了设计类的属性外,还需要为操作添加更多的详细信息。所以,除了在分析阶段为操作命名外,还需要详细描述操作的两个重要部分:参数列表和返回类型。如何确定操作的参数列表与返回类型并使用建模工具Rational Rose描述出来呢? 2018年9月21日星期五
二、设计操作 2. 解答问题 类操作的UML表示格式为: 可见性 操作名(参数列表):返回类型 以设计CustomerService类的操作为例,其结果见图7-20和图7-21所示。 2018年9月21日星期五
图7-20 CustomerService类的操作设计窗口 2018年9月21日星期五
二、设计操作 图7-21 CustomerService类的部分操作的UML表示
二、设计操作 3. 分析问题 在图7-21中,我们为CustomerService类定义了18个操作,详细情况见表8-2所示。 2018年9月21日星期五
表8-2 CustomerService类操作定义 操作名 返回类型 参数列表 功能描述 newCustomerService void csID:int(客户服务人员ID) name:String(姓名) sex:String(性别) age:int(年龄) phone:String(联系电话) dept:String(所属部门) rank:String(职位) password:String(密码) loginName:String(登录名) 新增客户服务人员 deleteCustomerService 删除一条客户服务人员信息 retrieveCustomerService 检索一条客户服务人员信息
二、设计操作 modifyCustomerService void csID:int(客户服务人员ID) 修改一条客户服务人员信息 createConsultation Consultation consultationID:int type:byte(咨询类型) consultationTime:String(咨询时间) content:String(咨询内容) state:String(所属状态) registrar:int(记录人) executant:int(跟进人) 创建一个来电咨询对象 deleteConsultation 删除一条来电咨询信息 retrieveConsultation 检索一条来电咨询信息
二、设计操作 modifyConsultation void consultationID:int 修改一条来电咨询信息 createExperienceBase ExperienceBase ebID:int type:String(类型) question:String(问题) solution:String(答案) catalog:String(所属目录) registrar:int(记录人) clock:Date(记录时间) 创建一个经验库对象 deleteExperienceBase 删除一条经验库信息 retrieveExperienceBase 检索一条经验库信息 modifyExperienceBase 修改一条经验库信息
二、设计操作 createCustomer Customer customerID:int customerName:String(客户名称) linkman:String(联系人) phone:String(联系电话) address:String(联系地址) 创建一个客户对象 deleteCustomer void 删除一条客户信息 retrieveCustomer 检索一条客户信息 modifyCustomer 修改一条客户信息 setName name:String(姓名) 设置客户服务人员姓名 getName String 得到客户服务人员姓名
二、设计操作 参数列表包含了0个或多个参数。如果参数个数多于1个,则参数之间用逗号(“,”)分隔。操作的参数描述了与操作执行相关的变量。我们来看看它的工作原理。在客户服务系统中,customerID属性用来唯一标识一个Customer对象。当某一个客户资料需要修改时,必须根据该客户的customerID来查找其相关信息,所以,Customer类的deleteCustomer操作需要customerID参数。
二、设计操作 就像属性的数据类型一样,参数的数据类型描述了操作所用的数据。所以,deleteCustomer操作的customerID参数的类型,与Customer类的customerID属性的类型一样,都是整型(int)。因此,可以说属性的数据类型决定了引用或改变此属性操作的参数的数据类型。
二、设计操作 操作的返回类型描述了当操作完成后操作返回给对象的数据类型。操作如果有返回值,可以返回一个像int这样的基本数据类型,或者把一个类作为返回类型。但有时操作是没有返回值的。在Java中用“void”表示操作没有返回值时的返回类型。
二、设计操作 通常用动词或动词短语为操作命名。操作名称里也没有空格,因此操作名称中包含的单词也是紧挨着的。人们习惯上会把首单词除外的其它单词的首字母大写,例如:getName。
二、设计操作 CustomerService类操作deleteCustomer在Rational Rose中的设计过程: ① 打开图7-15的对话框后,选择【Operations】选项页; ② 在列表框的空白处右击鼠标,弹出快捷菜单,选择【Insert】菜单项,此时,在列表框中新插入一项,修改操作名为“deleteCustomer”; ③ 双击该操作项,打开【Operation Specification for deleteCustomer】对话框;
二、设计操作 其过程结果如图7-22~8-23所示。 ④ 在【General】页的【Return】项输入返回类型:void; ⑤ 选择【Detail】选项页,在【Arguments】(参数列表)列表框的空白处右击鼠标,在弹出的快捷菜单中选择【Insert】菜单项; ⑥ 修改参数名、类型分别为“customerID”、“int” 如果操作有多个参数,则重复⑤、⑥两步。 其过程结果如图7-22~8-23所示。
二、设计操作 图7-22 设置操作deleteCustomer的返回类型为:void
二、设计操作 图7-23 设置操作deleteCustomer的参数customerID
二、设计操作 概念7-1:操作签名 解答:操作名、参数及其类型和操作的返回类型合在一起称为操作的签名。 扩展:一个类中,操作的签名必须具有唯一性,也就是说,一个类中的两个操作不能具有相同的签名。
二、设计操作 概念7-2:重载 解答:一个类中,具有相同名称和不同参数(指参数个数与参数类型的组合不同)的操作被称为“重载”。 扩展:具有相同名称和参数(参数个数和参数类型都相同)而返回类型不同的操作不是重载。因为调用操作时并不描述操作的返回类型,被调用的对象并不能分辨只是返回类型不同的两个操作。 操作的重载体现了面向对象的多态性,常常应用于面向对象的接口开发中。例如,我们在前面所举的例子中,Shape类的paint操作就是重载。Shape对象可以根据paint操作不同的参数画出不同的形状。
在建立分析模型时已经建立了类间的各种关系,但在设计阶段还需要对各种关系作进一步的细化处理。 7.4 设计类间关系 在建立分析模型时已经建立了类间的各种关系,但在设计阶段还需要对各种关系作进一步的细化处理。
7.4.1 设计继承 1. 问题引入 分析阶段所建立的继承关系没有考虑属性与操作的重组问题,为了加强重用性,细化分析阶段的继承层次可以减少代码量,有助于模型的一致性。这就意味着如果几个类继承了同一个超类,那么这几个类中相同的属性和操作将会做一些处理: 重新排列类的属性和操作。 将类分组以标识公共行为。 客户服务系统中,“系统用户”类与“部门领导”类、“客户服务人员”类、“维护人员”类、“系统管理员”类之间是继承关系。设计阶段如何来细化这种继承关系? 2018年9月21日星期五
7.4.1 设计继承 2. 解答问题 细化后类之间的继承关系如图7-24所示。 2018年9月21日星期五
5.8 职责分配 图7-24 细化后类之间的继承关系 2018年9月21日星期五
7.4.1 设计继承 3. 分析问题 “部门领导”、“客户服务人员”、“维护人员”和“系统管理员”这四个类性质相同,他们都是系统的用户,具有相同的属性(如姓名、性别、年龄、职位等)和操作(如增加信息、删除信息等),因此,我们将这些类共同的属性和操作抽取出来,抽象为超类“系统用户”(User)类,其它类由该类派生出来。由于add(增加)、delete(删除)、modify(修改)、retrieve(检索)这四个操作与派生类相关,也即是说,派生类不同,这四个操作的具体内容是不相同的,所以,需要将User类确定为抽象类,四个操作则在各自的派生类中实现。 2018年9月21日星期五
7.4.1 设计继承 大家请注意:类的“继承”关系虽然可以加强重用,但“继承”又加剧了对象之间的依赖性。滥用“继承”,往往会给软件维护带来极大的麻烦!例如,图7-25中,将不同领域的类之间形成继承关系,这是必须避免的。 2018年9月21日星期五
7.4.1 设计继承 图7-25 不同领域的类之间形成错误的继承关系 2018年9月21日星期五
下面仅介绍聚合/组合关系的代码映射,以加深读者对聚合/组合关系的理解。 7.4.2 设计聚合/组合 继承关系加强了类之间的耦合性,而聚合/组合关系则可以解耦合、增强类的内聚性。在设计阶段通常要把一般的关联关系细化为聚合/组合关系。类与类之间的聚合/组合关系详见5.3。 下面仅介绍聚合/组合关系的代码映射,以加深读者对聚合/组合关系的理解。 2018年9月21日星期五
7.4.2 设计聚合/组合 1. 假如类A与类B之间有如图7-26所示的一对一的单向聚合关系,则代码映射为: public class A { protected B theB; //类B作为类A的一个成员变量 …… } public class B { 图7-26 一对一的聚合加单向关联
7.4.2 设计聚合/组合 2. 假如类A与类B之间有如图7-27所示的一对多的单向聚合关系,则代码映射为: public class A { protected ArrayList theBs; //类B的对象数组 …… } public class B { 图7-27 一对多的聚合加单向关联 2018年9月21日星期五
7.4.2 设计聚合/组合 3. 假如类A与类B之间有如图7-28所示的一对一的单向组合关系,则代码映射为: public class A { protected class B { …… } protected B theB; 图7-28 一对一的组合加单向关联 2018年9月21日星期五
7.4.2 设计聚合/组合 4. 假如类A与类B之间有如图7-29所示的一对多的单向组合关系,则代码映射为: public class A { protected class B { …… } protected ArrayList theBs; //类B的对象数组 图7-29 一对多的组合加单向关联 2018年9月21日星期五
7.4.3 设计关联 设计模型中需要细化的关联主要是关联的导航方向。在分析阶段通常不会考虑到导航方向,其分析模型中关联的导航往往是双向的,然而实现双向关联要比实现单向关联困难得多,所以,在设计阶段,必须根据实际情况,将有些关联确定为单向导航。例如,在客户服务系统中,分析时“客户”和“来电咨询”之间的关联是双向的,但在设计时就会把它们的关联设为单向的。因为“客户”可以访问“来电咨询”,而“来电咨询”不能访问“客户”。 除非两个类都需要从对方获得信息,才需要建立双向关联,否则最好建立为单向关联,以简化问题的实现。 2018年9月21日星期五
7.5 活动图 1. 问题引入 活动图是一种对动态行为建模的交互图。它常用来对业务流程建模,但您也可以使用它对对象在交互期间执行的操作(类的操作)建模。在Rational Rose中如何建立活动图? 2018年9月21日星期五
7.5 活动图 2. 解答问题 以客户服务人员处理来电咨询为例建立活动图,其结果如图7-30所示。 2018年9月21日星期五
7.5 活动图 图7-30 客户服务人员处理来电咨询活动图
7.5 活动图 3. 分析问题 图7-30中,当“客户来电”事件发生后,进入“来电咨询”活动,如果受理,则查询客户信息,否则,活动结束;当查询客户信息时,如查询到该客户,则判断咨询类型,否则新增一个客户的信息;咨询类型有三种:咨询、投诉、报障,如果是咨询,判断是否是能解答的问题,如果能,则直接处理,否则,由维护人员跟进;如果是投诉,转入投诉处理;如果是报障,则转入故障处理。咨询处理结束后,填写咨询处理结果,整个活动流程自动结束。
7.5 活动图 一个基本活动图通常具有以下元素: 开始点:用填充的圆圈表示。在同一个包的同层次活动图中,只允许存在一个开始点。 结束点:活动图可以有一个或多个结束点,用牛眼符号表示。 活动:表示工作流程中的任务或步骤。用环绕着活动文本的圆头矩形表示。如图7-30中的“来电咨询”、“查询客户信息”等。
7.5 活动图 活动转换:用来显示活动状态的先后顺序。这种类型的转换通常称为完成转换,它不需要显式的触发器事件,是直接通过任务完成来触发。活动之间的转换用带箭头的连接线表示。从一个活动转换到另一个活动,如:当有“来电咨询”后,如果“受理”了,下一步要做的就是“查询客户信息”,因此,就从“来电咨询”活动转换到了“查询客户信息”活动。整个活动流程按箭头的流向进行。
7.5 活动图 活动图可以说明在转换发生之前必须为“真”的警戒条件,用方括号括起来的内容,如图7-30中的“受理”、“不受理”等是转换发生前的警戒条件。在Rational Rose的活动图中,双击转换线,打开【State Transition Specification】对话框,在【Detail】页的【Guard Condition】栏中输入警戒条件,如“受理”。如图7-31所示。
7.5 活动图 图7-31 警戒条件
7.5 活动图 还可以为活动图的转换给出一些详细说明。Send子句可以给转换关联指定在活动转换期间发送的消息,包括消息名称、发送消息的参数以及发送消息的目标(消息的接收对象)。如果7-31所示。
7.5 活动图 决策:用判断菱形表示。为其定义一组警戒条件。警戒条件控制当某个任务完成时转换到一组备选转移中的哪一个转移。通常用在两个或两个以上分支的情况。 如图7-30中,当有来电咨询,需要查询有没有要咨询的客户时,有两个分支:有客户和没有客户,其处理步骤不同,因此需要两个可选流程。 我们也可以使用决策图标来显示这些流程在何处再次合并。比如,在图7-30中,当“咨询”、“报障”、“投诉”处理结束后,将这三个分支流程合并起来。
7.5 活动图 同步条:用来处理并行子流程。并行活动按什么顺序发生并不重要,它们可以同时发生,或者可以交迭发生。同步条还用于将同步发生的活动集合起来,这就意味着向外的转换仅在所有向内的转换发生之后才发生。如图7-32所示的粗水平线即为同步条,图中“准备货物”和“开发票”两个活动的先后顺序无明确规定,既可以同时发生,也可以交迭发生。 同步条有两种:一种是在上面已经提到的水平同步条,另一种则是垂直同步条。一般地,如果分叉的多个活动横向排列时,使用水平同步条;当多个活动竖向排列时,则使用垂直同步条。
7.5 活动图 图7-32 带同步条的活动图
7.5 活动图 4. 问题扩展:泳道 从上面的基本活动图中,我们发现活动图有一个主要缺点:它没有显示出由谁或者什么负责来执行某项活动。当用活动图对业务过程建模时,它没有显示哪个部门或人负责哪些活动。当用活动图对对象交互建模时,它没有显示出由哪个对象对哪些活动负责。如何解决这个问题呢? 解答:为了给活动图中的活动指明责任者,必须在活动图中放置泳道。泳道是将图划分为职责域的垂直线。
7.5 活动图 在图7-33中,我们看到客户服务人员处理来电咨询活动图被泳道分成了两个职责域,每个域有一个标题,说明在这个域中负责执行活动的对象。例如,Customer(客户)对象负责“来电咨询”活动,而CustomerService(客户服务人员)则负责“查询客户信息”、“新增客户信息”等活动。 使用泳道可以把整个活动图组织成职责域,增强了活动图的可理解性。
7.5 活动图 图7-33 使用泳道的客户服务人员处理来电咨询活动图
7.5 活动图 推荐:在建立活动图时,最好先给出泳道,然后把活动添加到相应的泳道上。这是因为如果在创建活动图的最后阶段才使用泳道,会使整个活动图的整理过程变得很困难,相当于重新创建一次活动图,效率极其低下。
活动图的优缺点 (1) 活动图的优点 活动图与顺序图和协作图相比,主要有两个优点: 可以对平行行为建模。活动图因为有显示平行活动的能力,所以很适合为多线程应用和并发应用建模。 可以显示多个用例如何相互关联。这样,可以使用活动图获得一个系统中构件是如何交互的。 另外,活动图还可以用来优化业务流程。在业务建模的早期,发现业务流程不合理,及时纠正,以优化业务流程。
活动图的优缺点 (2) 活动图的缺点 活动图的主要缺点就是,只有使用泳道,才能清楚地显示出该由哪个对象对哪个活动负责。但在复杂的活动图上,涉及的泳道很多,使整个活动流程变得很不清晰,影响了人们对活动图的理解。
7.6 状态图 1. 问题引入 状态图描述了一个对象基于事件反应的动态行为,显示了该对象所处的可能状态以及状态之间的转换,并给出了状态变化的起点和终点。 状态图与活动图很相似。两种图的不同之处主要表现在:状态图以状态为中心,而活动图则以活动为中心。状态图通常用来对一个对象生命周期的离散的状态建模,而活动图更适合于对一个流程中的一系列活动建模。 状态图通常用于显示具有复杂行为和经历许多状态之间转换的类。不必为系统中每个类都建立状态图。只有当行为的改变和状态有关时才创建状态图。如何在Rational Rose中建立状态图? 2018年9月21日星期五
以客户服务系统中的“派工单”为例,创建状态图。“派工单”状态图如图7-34所示。 7.6 状态图 2. 解答问题 以客户服务系统中的“派工单”为例,创建状态图。“派工单”状态图如图7-34所示。 2018年9月21日星期五
7.6 状态图 图7-34 “派工单”状态图
7.6 状态图 3. 分析问题 在图7-34中,“派工单”有五个状态:新派工单、未分配、已分配未完成、已分配已完成、删除派工单。当某一事件发生或某个条件满足时,就在这五个状态之间进行转换。图中还包含了一个开始状态和一个结束状态。对象的状态图具有以下元素: 2018年9月21日星期五
7.6 状态图 开始状态:由一个实心圆圈表示。每个类必须有一个开始状态,并且只能有一个开始状态。 终止状态:由一个牛眼符号表示。终止状态是可选的,对象可以有多个终止状态。 状态:由环绕着状态文本的圆角矩形表示。一个状态表示一个对象在其生命周期中满足某个条件或等待某个事件发生的一个条件或情形。每个状态代表了它的行为轨迹。 状态转换:由带箭头的直线表示。一个状态转换表示一个对象在源状态将执行某些特定的动作,并当某个特定的事件发生或某些条件满足时,进入目标状态。状态转换是两个状态之间的关系。 2018年9月21日星期五
状态转换是互斥的。因为对象不能同时转换到多种状态,所以,在状态图中每次只能有一个向外的转换发生。状态转换的语法格式: 7.6 状态图 状态转换是互斥的。因为对象不能同时转换到多种状态,所以,在状态图中每次只能有一个向外的转换发生。状态转换的语法格式: 事件(参数列表) [警戒条件] / 动作^ 目标.发送事件(参数) 2018年9月21日星期五
7.6 状态图 图7-34中,“待分配”、“已分配”、“完成”等是状态转换所发生的事件(Event),“客户已确认”则是从状态“已分配未完成”转换到状态“已分配已完成”的警戒条件(Guard Condition),而“客户签字”则是转换的动作(Action)。 每个转换仅允许有一个事件,每个事件只能有一个动作。 2018年9月21日星期五
7.6 状态图 4. 问题扩展:子状态 简单状态是指不含子结构的状态。如图7-34中所有的状态都不含有子结构,因此这些状态都是简单状态。含有子状态(内嵌状态)的状态被称为组合状态。在状态图中一个状态可以内嵌任意层的子状态。子状态通过显示仅在特定环境(封闭状态)内可能存在的某些状态,用以简化复杂的状态图。
7.6 状态图 子状态是一种包含在超状态中的状态。图7-35中显示了三个子状态:未分配、已分配未完成和已分配已完成,它们都内嵌在“处理派工单”超状态中。在嵌套状态中还可以包含一个开始状态和至少一个终止状态。
7.6 状态图 图7-35 含有子状态的组合状态图
7.6 状态图 从整体上看,组合状态图减少了转换数,组织结构更有逻辑性,并且对象生命周期也更容易观察,因此,组合状态图减少了图形的复杂性,更适合用于对更大、更复杂的问题建模。
7.7 设计模型顺序图 1. 问题引入 我们在第5章的分析建模中已对顺序图有了比较详细的了解,在这里为什么还要谈及顺序图呢?这是因为在分析阶段只是对业务逻辑进行了比较高层次的抽象,所建立的分析模型是领域模型,没有考虑任何实现技术,分析出来的类仅包括边界类、控制类和实体类。因此,在分析模型的基础上,我们还必须对其进行细化。考虑设计模式(有关设计模式的问题参阅本章的第一节)、数据处理方式以及分层结构等。这样,用例中的消息流程将变得更加复杂。为了增强模型的可读性与可理解性,有必要在设计阶段细化顺序图,也因此发现更多的设计类。如何细化分析阶段的顺序图呢?
7.7 设计模型顺序图 2. 解答问题 现以B/S三层体系结构为例。随着软件工程的不断发展和规范以及面向对象编程思想的日益成熟,人们对封装、复用、扩展、移植等方面的深刻认识,产生了三层架构体系。所谓“三层”,是指表示层、业务层和数据层。这三层只是逻辑上的三层。通过引入业务层,将复杂的业务逻辑从传统的两次(C/S)应用模型中分离出来,并提供了可伸缩、易于访问、易于管理、易于维护的方法,同时增强了应用程序可用性、安全性、封装复用性、可扩展性和可移植性,从而实现了便捷、高效、安全、稳定的企业级系统应用。
考虑三层体系结构以后,顺序图应细化为图7-36的形式。 7.7 设计模型顺序图 考虑三层体系结构以后,顺序图应细化为图7-36的形式。
图7-36 细化后的顺序图
7.7 设计模型顺序图 3. 分析问题 我们从图7-36中可以看出,Actor和边界类属于表示层,业务层包括控制类与实体类,而数据层则包含了数据服务类、数据访问类和存储。所有业务逻辑封装在实体类中;而对各种数据库的访问,如数据库的连接、打开、事务处理、关闭等,则封装到数据访问类,提供数据服务的接口;各种业务请求,如增加、删除、修改等,被封装到数据服务类中。 将在细化顺序图时增加的数据服务类和数据访问类添加到类图中。 细化后的顺序图包含了数据的处理流程。增加了数据服务类和数据访问类,提高了构件的重用性,同时也增强了系统的可维护性。
7.8 设计模型的分包 我们可以将设计模型分解成较小的单元,以使其更易于理解。通过将设计模型元素分组成包和子系统,然后显示这些组如何互相关联,可以更容易理解模型的整体结构。大家注意,设计模型的分包只是用于设计类的分组。例如,客户服务系统的包结构,如图7-37所示。
7.8 设计模型的分包 图7-37 客户服务系统设计模型包结构
7.8 设计模型的分包 概念7-3:包内容的可见性 解答:包中包含的类可以是公有的或私有的。所有其它类都可以和公有类相关联,但私有类只可以和包中包含的类相关联。包接口由包的公有类组成。包接口(公有类)分隔其它包并与其它包产生依赖关系。
7.8 设计模型的分包 1. 问题引入 软件设计项目千差万别,随着网络技术的不断发展,项目的规模越来越大,也越来越复杂,为了便于理解,我们在建模时通常会把模型元素分组成包或子系统。对于小型系统来说,分包比较容易,但对于大中型系统,如果没有一个统一的规则,可能会引起许多混乱,导致模型的可读性变差。那么,对于设计模型的分包究竟有什么规律可循呢?
2. 解答问题 7.8 设计模型的分包 根据RUP设计模型的分包指南,可以从以下几个方面来确定包结构。 (1) 封装边界类 当将边界类分发到包时,可以应用两种不同的策略,选择哪一种策略应取决于将来是否会大幅度地更改系统接口。
7.8 设计模型的分包 ① 如果有可能会替换系统接口,或进行大量更改,则应将该接口与设计模型的其余部分分开。更改用户接口时,只影响这些包。如果主要目标是简化重大接口的更改,则应将边界类放置在一个(或几个)单独的包中。
7.8 设计模型的分包 ② 如果没有打算进行重大接口更改,则应将边界类和与其功能相关的实体类和控制类放在一起。这样,就能够容易地看到在更改某个实体类或控制类的情况下将影响哪些边界类。 为简化对系统服务的更改,往往将边界类和与其功能相关的类封装在一起。对于在功能上与任何实体类或控制类都不相关的必需边界类,应将它们和属于同一接口的边界类放置在单独的包中。
7.8 设计模型的分包 (2) 封装功能相关的类 应为功能相关的每组类确定一个包。 当判断两个类是否功能相关时,可以应用下面几个条件: ① 如果一个类的行为和/或结构中的更改使另一个类中也有必要更改,则这两个类在功能上相关。 ② 可以通过以一个类(例如,实体类)开始并检查从系统中删除它会有什么影响,来判断这个类是否与另一个类在功能上相关。所有由于删除某个类而变得多余的类都与被删除的类有某种联系。多余性表示只有被删除的类才使用该类,或该类自身依赖于被删除的类。
7.8 设计模型的分包 ③ 如果两个对象使用大量消息交互或有其它方式的复杂的相互通信,则它们可以是功能相关的。 ④ 如果边界类的功能是显示特定实体类,则边界类可与该特定实体类功能相关。 ⑤ 如果两个类与同一个参与者交互或受到同一参与者中的更改的影响,则这两个类可以是功能相关的。如果两个类不涉及相同的参与者,则它们不应放在相同的包中(当然可以由于更重要的原因而忽略这个规则)。
7.8 设计模型的分包 ⑥ 如果两个类之间有关系(关联、聚合、组合等),则这两个类可以是功能相关的。当然,不能盲目地遵循该条件,但当没有其它条件适用时,可以使用它。 ⑦ 某个类可以与创建该类实例的类功能相关。 以下两个条件可以用于确定不应将两个类放在相同的包中: 与不同参与者相关的两个类不应放到相同的包中。 不应将可选类和强制类放到相同的包中。
7.8 设计模型的分包 3. 分析问题 如果一个包中的类与不同包中的类有关联,则这些包互相依赖。应使用包之间的依赖关系对包依赖建模。包之间的依赖关系体现了包之间的耦合程度。如果包与包之间有太多或太复杂的依赖关系,则会使系统变得难以维护。
包耦合有好处也有坏处:好处是因为耦合代表了重用,坏处是因为耦合代表了使系统难于更改和演化的依赖关系。包依赖可遵循一些原则: 7.8 设计模型的分包 包耦合有好处也有坏处:好处是因为耦合代表了重用,坏处是因为耦合代表了使系统难于更改和演化的依赖关系。包依赖可遵循一些原则:
7.8 设计模型的分包 不应交叉耦合(即交叉依赖)包。例如,两个包不应互相依赖,如图7-38所示。在这些情况中,需要将包重新组织以除去交叉依赖关系。 图7-38 两个包A和B互相依赖
7.8 设计模型的分包 下层中的包不应依赖于上层中的包。包应仅依赖于同一层和次下层中的包。如图7-39中的情形应该避免。如果出现了这种情况,应该将功能重新分区。一种解决方案是按照接口声明依赖关系,并组织下层中的接口。 图7-39 下层包依赖于上层包
7.8 设计模型的分包 通常情况下,除非依赖行为在所有层之间是公共的,否则依赖关系不得跳层,另一可选方法是简单地在各层上传递操作调用。 包不应依赖于子系统,仅应依赖于其他包或接口。
7.9 逻辑视图到构件视图的映射 设计模型完成后,需要检查整个模型的正确性。如在Rational Rose中,可选择【Tools】->【Check Model】菜单项,由建模工具自动检查模型。如果所建模型没有错误,就可以选择某种程序设计语言,由系统自动生成程序代码;或自动生成关系数据库了。但在自动生成程序代码或关系数据库之前,需要将逻辑视图映射到构件视图。
一、逻辑视图包到构件视图包的映射 7.9 逻辑视图到构件视图的映射 模型逻辑视图中的信息是通过把逻辑视图包映射到构件视图包这种方式和模型的构件视图中的信息联系在一起的。通常,逻辑视图包和物理视图包是直接联系在一起的。但是,有时也没有必要进行一对一的映射。这是因为:
7.9 逻辑视图到构件视图的映射 逻辑视图包可能会因为实现的原因而被分开。 逻辑视图包可能会为了对象之间交流更加紧密而被合并。 为了实现底层功能可能会加入分析中没有的物理视图包。
在Rational Rose中把逻辑包映射到构件包的方法: 7.9 逻辑视图到构件视图的映射 在Rational Rose中把逻辑包映射到构件包的方法: (1) 在构件视图(Component View)中选择构件包; (2) 把它拖至逻辑视图(Logical View)中相应的包。
这时,在逻辑视图的包上多了一个用圆括号括起来的包名,该包就是构件视图相应的构件包。映射后的视图如图7-40所示。 7.9 逻辑视图到构件视图的映射 这时,在逻辑视图的包上多了一个用圆括号括起来的包名,该包就是构件视图相应的构件包。映射后的视图如图7-40所示。 图7-40 包的映射
7.9 逻辑视图到构件视图的映射 二、把类映射到构件上 在Rational Rose中将类映射到构件上的方法: (1) 选择构件视图(Component View)中的某个包中的构件; (2) 将该构件拖曳至逻辑视图(Logical View)中相应包的相应类上,即完成了类到构件的映射。 例如:将构件视图中的bussiness包的CustomerService(客户服务人员)构件拖曳至逻辑视图中bussiness包的CustomerService类上。即可完成CustomerService类到CustomerService构件的映射。如图7-41所示。
7.9 逻辑视图到构件视图的映射 图7-41 类到构件的映射
7.9 逻辑视图到构件视图的映射 三、生成程序代码 在Rational Rose中生成程序代码的步骤: 第一步 为构件设定程序设计语言 要生成程序代码,必须选定程序设计语言,其方法是: (1) 右击浏览窗口中的构件视图(Component View)中某个包中的构件,如Customer构件,弹出快捷菜单; (2) 选择【Open Specification】项,打开构件的规格设定对话框; (3) 在Language框中选择相应的语言,比如:Java,如图7-42所示; (4) 点按Ok按钮关闭对话框。
7.9 逻辑视图到构件视图的映射 图7-42 为构件Customer指定程序设计语言
7.9 逻辑视图到构件视图的映射 第二步 生成程序代码 当选定程序设计语言后,就可以生成程序代码了,其过程如下: (1) 右击浏览窗口中Component View中某个包中的构件,比如:Customer,弹出快捷菜单; (2) 选择【Java/J2EE->Generate Code】项,弹出如图7-43所示的对话框; (3) 点击【Edit…】按钮,在弹出如图7-44所示的对话框中,点击【New(Insert)】按钮,在增加的一项后边,点击带三个点的按钮,弹出如图7-45所示的对话框; (4) 点击【Directory…】按钮,给生成的Java文件指定放置路径并确定; (5) 返回到图7-43的对话框中,选择添加进来的路径并点击【Assign】按钮; (6) 点击Ok按钮,即可完成一个构件的生成。
7.9 逻辑视图到构件视图的映射 图7-43 生成Java文件对话框
7.9 逻辑视图到构件视图的映射 7-44 类路径管理对话框
7.9 逻辑视图到构件视图的映射 7-45 新增类路径
四、构建数据库结构 7.9 逻辑视图到构件视图的映射 1. 实体类映射到关系数据库 我们先来看看用手工方式如何将要保存到数据库中信息所对应的实体类映射到关系数据库。 在设计模型中,我们已对实体类以及实体类之间的关系建模,这样我们就可以直接把实体类及其关系映射到关系数据库。类可以映射为表,对象则映射为行(或记录),属性映射为列(或字段)。
(1) 泛化(继承)关系的映射 7.9 逻辑视图到构件视图的映射 如图7-24的泛化关系,可以将超类和子类都映射成表,超类的主键作为所有子类的外键;也可以仅将子类分别映射成表,表的字段为各自子类属性加超类属性,每个表的主键分别定义。
7.9 逻辑视图到构件视图的映射 (2) 一对多关联关系的映射 图7-46 Customer类与Consultation类之间的一对多关联关系
7.9 逻辑视图到构件视图的映射 图7-47 图7-456的关系数据库的映射
从图7-47中可以看出,外键放在关联关系多的一端。PK指的是主键,而FK是指外键。 7.9 逻辑视图到构件视图的映射 从图7-47中可以看出,外键放在关联关系多的一端。PK指的是主键,而FK是指外键。
7.9 逻辑视图到构件视图的映射 (3) 一对一关联关系的映射 图7-48 Customer类与Consultation类之间的一对一关联关系
7.9 逻辑视图到构件视图的映射 图7-49 图7-48的关系数据库的映射
从图7-49可以看出,如果两个类是一对一的关系,则外键可以放在其中任意一个表中。 7.9 逻辑视图到构件视图的映射 从图7-49可以看出,如果两个类是一对一的关系,则外键可以放在其中任意一个表中。
(4) 多对多关联关系的映射 7.9 逻辑视图到构件视图的映射 如果两个类之间是多对多的关联关系,例如“学生”与“课程”之间的关系(如图7-50所示),假如要记录学生的考试成绩,则在映射关系数据库时,必须添加第三个表:“成绩”表。“学生”表和“课程”表的主键作为“成绩”表的外键。如图7-51所示。
7.9 逻辑视图到构件视图的映射 图7-50 “学生”类与“课程”类之间的多对多连接关系
7.9 逻辑视图到构件视图的映射 图7-51 图7-50的关系数据库的映射
(5) 一对多的聚合/组合关系 7.9 逻辑视图到构件视图的映射 同一对多的关联关系类似,“整体”表的主键作为“部分”表的外键,放在“部分”表中。
2. 用建模工具自动映射 7.9 逻辑视图到构件视图的映射 使用建模工具,如Rational Rose,可以将设计类图中实体类及实体类之间的关系直接映射为关系数据库。下面的内容中,我们用两个实体类Consultation和Customer及这两个类之间的关系,来简要描述其自动映射过程。Consultation类与Customer类之间的关系如图7-46所示。
7.9 逻辑视图到构件视图的映射 在Rational Rose中,将类及类之间的关系映射为关系数据库的方法如下: (1) 设置实体类的persistence属性(持久性)为persistent(永久的) ① 在Rational Rose浏览窗口的Logical View中选择Consultation类,点击鼠标右键,在弹出的快捷菜单中选择【Open Specification】菜单项,打开“【Class Specification for Consultation】对话框,如图7-52所示; ② 选择【Detail】页,在Persistence栏中选择Persistent项,如图7-53所示; ③ 这样就把Consultation类设置成了永久类。 设置Customer类为永久类,其方法与Consultation类的设置方法类似。
7.9 逻辑视图到构件视图的映射 图7-53 设置类为永久类 图7-52 Consultation类的规格说明对话框
(2) 把包含了实体类的包转换成数据库的数据模型 其方法是: 7.9 逻辑视图到构件视图的映射 (2) 把包含了实体类的包转换成数据库的数据模型 其方法是: ① 在Rational Rose浏览窗口的Logical View中右击包含了Consultation类和Customer类的Service包,弹出快捷菜单,并选择【Data Modeler】->【Transform to Data Model …】,弹出【Transform Object Model to Data Model】对话框; ② 在Destination Schema中输入“db”,在Prefix中保留默认的表前缀“T_”,并勾选“Create Indexes for Foreign Keys”,如图7-54所示,点击“Ok”按钮。
7.9 逻辑视图到构件视图的映射 图7-54 生成数据模型对话框
7.9 逻辑视图到构件视图的映射 这样,就在浏览窗口的Logical View中生成了“Schemas”包和“<Schema>db”子包。子包中生成了以“T_”为前缀的表。
(3) 创建Data Model Diagram 7.9 逻辑视图到构件视图的映射 (3) 创建Data Model Diagram 右击浏览窗口中的【Logical View】->【Schemas】->【<Schema>db】,弹出快捷菜单,选择【Data Modeler】->【New】->【Data Model Diagram】,生成一个Data Model Diagram,取名为Main; 双击Main,打开Diagram,将生成的表拖进Diagram,生成E-R图。如图7-55所示。
7.9 逻辑视图到构件视图的映射 图7-55 自动生成的E-R图
(4) 生成SQL文件 7.9 逻辑视图到构件视图的映射 右击浏览窗口中的【Logical View】->【Schemas】->【<Schema>db】,弹出快捷菜单,选择【Data Modeler】->【Forward Engineer …】,按照Wizard的提示进行操作,将生成的SQL语句保存在文件中。
总结 软件设计模式是在面向对象的软件系统设计过程中,对不断发现的软件设计问题的重复解决方案。 创建优秀的面向对象设计的三条通用建议: 针对接口设计; 优先使用对象组合,而不是类继承; 找到并封装变化点。 常用的软件设计模式主要有Facade模式、Bridge模式、Factory模式、Strategy模式、Adapter模式、State模式等。软件系统设计中需要根据实际情况应用设计模式,往往一个系统会应用其中的多个设计模式。 应用设计模式会提高软件系统的可维护性,但一般不会对系统的功能和性能有影响。 2018年9月21日星期五
总结 分析模型中的类都是分析类,包括边界类、控制类和实体类。分析类展示的是高层次的属性和操作的集合,面向问题域,其类图称为领域模型。 分析模型是设计模型的输入,设计模型是把实现技术加入分析模型后对分析模型的细化,考虑了设计模式等因素。 设计类时要考虑其职责的单一性,一个类不能太大或者太复杂,否则,难以维护和改变。 一个完善的类操作一般会提供四个方面的功能:实现功能、访问功能、管理功能和辅助功能。当然,也有可能是其中的一部分功能。 设计类时通常会将一些公共算法封装成特定的类,这种类被称为公用类。 设计类接口时最好用一系列设计良好的简单操作对行为建模,既能提高接口的内聚性,也使接口更容易被重用。 操作名、参数及其类型和操作的返回类型合在一起称为操作的签名。一个类中不能存在相同的签名,即操作的签名必须是唯一的。 2018年9月21日星期五
总结 一个类中,具有相同名称和不同参数的操作称为重载。但具有相同名称和参数而返回类型不同的操作不是重载。 设计继承关系时,需要将子类公共的属性和操作放入超类中,以体现其重用性。超类通常被设计为抽象类。 继承关系加强了类之间的耦合程度,会降低系统的可维护性,因此不要滥用继承关系。 活动图通常用于对业务流程建模,可以发现业务流程存在的缺陷,以优化业务流程。 活动图与顺序图和协作图相比,其主要优点是可以对平行行为建模,以及可以显示多个用例的相互关联性。 状态图用于对对象的动态行为建模,用于显示具有复杂行为和经历许多状态之间转换的类。不必为系统中每个类都建立状态图。 2018年9月21日星期五 第229页