设计模式与体系结构

设计模式与体系结构

设计模式

1 面向对象设计概述

面向对象设计原则

在这里插入图片描述
在这里插入图片描述

单一职责原则

定义:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中

开闭原则

开闭原则定义如下:

  • 一个软件实体应当对扩展开放,对修改关闭
  • 也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即在不修改源代码的情况下改变这个模块的行为。

里氏代换原则

定义:所有引用基类(父类)的地方必须能透明地使用其子类的对象。

  • 在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象

依赖倒转原则

依赖倒转原则的定义如下:

  • 高层模块不应该依赖低层模块,它们都应该依赖抽象。
  • 抽象不应该依赖于细节,细节应该依赖于抽象。

另一种表述为:要针对接口编程,不要针对实现编程

简单来说,依赖倒转原则就是指:

  • 代码要依赖于抽象的类,而不要依赖于具体的类;
  • 要针对接口或抽象类编程,而不是针对具体类编程。

接口隔离原则

定义:客户端不应该依赖那些它不需要的接口,一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。

  • 接口隔离原则是指使用多个专门的接口,而不使用单一的总接口

合成复用原则

定义如下:

  • 尽量使用对象组合,而不是继承来达到复用的目的。
  • 合成复用原则就是指在一个新的对象里通过组合关系和聚合关系来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。

简言之:要尽量使用组合/聚合关系,少用继承

迪米特法则

几种典型定义如下:

  1. 不要和“陌生人”说话。
  2. 只与你的直接朋友通信。
  3. 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

简言之:一个软件实体应当尽可能少的与其他实体发生相互作用。

2 创建型设计模式

  • 根据其目的(模式是用来做什么的),面向对象的领域的设计模式可分为创建型(Creational),结构型(Structural)和行为型(Behavioral)三种:

    1. 创建型模式主要用于创建对象。
    2. 结构型模式主要用于处理类或对象的组合。
    3. 行为型模式主要用于描述对类或对象怎样交互和怎样分配职责。
  • 动机

    • 在面向对象编程中,在编写创建对象的代码时,往往需要设置许多条件语句,以便决定在什么条件下、何时、 如何创建某个类的对象。因此,有必要将创建对象的责任委托给某个特殊的类
  • 目标

    • 其目的是在哪个对象被创建、谁负责创建对象、怎样创
      建对象、何时创建对象
      等方面增强灵活性。
  • 内容

    • 创建型软件设计模式是解决对象创建机制的设计模式。 本章将介绍其中的简单工厂方法模式、工厂方法模式、 抽象工厂模式、生成器模式和单例模式
  • 创建型软件设计模式是为了将创建对象的责任委托给某个特殊的类,所以注意被创建的对象和谁创建,怎样创建,增强代码的灵活性
  • 创建和使用分离
  • 创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。
    在这里插入图片描述

2.1 工厂方法与抽象工厂模式

概念

  • 工厂方法的优点:
    • 使用工厂方法访问并初始化合适的类的对象,简化了应用程序,应用程序本身不再含有大量的条件语句判定何时选取哪个类
    • 工厂方法实现了一些特殊的初始化某个类的机制,尤其是层次结构不同的类需要不同的初始化方法的时候。
    • 工厂方法返回一个父类的对象,客户程序不必知道这个被初始化的类的存在
2.1.1 简单工厂方法模式(不支持开闭原则)
  • 简单工厂模式:又称为静态工厂方法模式。

  • 在简单工厂模式中,可以根据参数的不同返回不同类的实例

  • 简单工厂模式专门定义一个类(工厂类)来负责创建其他类的实例,被创建的实例通常都具有共同的父类
    在这里插入图片描述
    (应为虚线箭头,依赖关系——消息关系)

  • 优点

    1. 工厂类包含从一个类的结构中选择初始类的业务逻辑,
      实现了责任分离。
    2. 客户类不负有创建类的对象的责任,没有创建对象所需要的条件语句,因此如果有新产品子类加入,不必修改 已有的客户类代码。
  • 缺点

    1. 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响
    2. 系统扩展困难,工厂类必须知道怎样创建每个子类的对象,每当增加产品类的时候,都需要修改工厂类的代码
    3. 工厂角色无法形成基于继承的等级结构

    简单工厂模式最大的缺点就是当有新产品要加入到系统中时,必须修改工厂类,加入必要的处理逻辑,这违背了“开闭原则”
    并且要是工厂类失效,整个系统都失效了
    类的个数太多

  • 适用场景
    所以一般只有在对应的类少,以及客户端只需要提供参数就得到对应的对象的情况

2.1.2 工厂方法模式(支持开闭原则)
  • 将简单工厂方法模式中单一的工厂类改写为一个层次类
    解决简单工厂模式的工厂角色无法形成基于继承的等级结构的问题,可以用和产品类相同的结构产生创建者类结构
    每个产品类对应一个工厂类,该工厂类只负责创建相应产品类的对象
    应为虚线箭头,表示依赖
    (应为虚线箭头,依赖关系——消息关系)

在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做
工厂方法模式退化后可以演变成简单工厂模式

  • 优点:

    1. 用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名(因为一个工厂创建一个产品)
    2. 工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部
    3. 在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了
  • 缺点:

    1. 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。难以应对具有两组以上产品类的情况。
  • 简单工厂模式与工厂方法模式的区别

    • 两个模式的中心不同。
      • 简单工厂模式的中心为一个实的工厂类,
      • 工厂方法模式的中心为抽象工厂或者接口
    • 是否支持开闭原则。
      • 简单工厂模式不支持
      • 工厂方法模式支持
    • 创建对象逻辑判断的位置。
      • 简单工厂模式中,必要的创建对象的逻辑判断包含在工厂类中(实例一:汽车保险管理程序)
      • 在工厂方法模式中,工厂类不必包含创建对象的逻辑判断
  • 工厂方法模式的适用场景

    • 创建某些类的对象的逻辑比较复杂,而且还可能增加新
      的条件
    • 需要封装创建类的对象的逻辑,使得这些逻辑局部化
    • 一个类不知道它所需要的对象的类
    • 一个类通过其子类来指定创建哪个对象(子类指定创建的对象)
    • 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定
2.1.3抽象工厂模式

产品等级概念(继承同一东西)与产品族(同一个工厂下的不同继承不同东西)
在这里插入图片描述

需要一个工厂可以提供多个产品对象,而不是单一的产品对象簇。

在这里插入图片描述

Creator cr = null;
if ( x ==a ) cr = new ConcreteCreatorA();
else if ( x == b ) cr = new ConcreteCreatorB();
if ( y =ManTie) ManTie mt = cr.factoryA();
else if ( y =WomanTie) WomanTie wt = cr.factoryA();
else if ( y =ManShoes) ManShoes ms = cr.factoryB();
else if ( y =WomanShoes) WomanShoes ws = cr.factoryB();
else if ( y =ManSuit) ManSuit msi = cr.factoryC();
else if ( y =WomanSuit) WomanSuit wsi = cr.factoryC(): 

  • 抽象工厂模式的可扩展性
    在这里插入图片描述
    • 增加一个新的产品族,新的品牌的产品(没有新增产品的种类),就只需要一个新的具体工厂,不需要修改工厂接口,符合开闭原则
      在这里插入图片描述
    • 增加已有产品族的产品,即在原先的产品族中新增一个类别的产品(苹果原有手机、耳机,新推出手表),需要修改具体的工厂和工厂接口,不符合开闭原则
      在这里插入图片描述
  • 例如原来有两家公司A和B生产电视和冰箱,(工厂接口有生产电视和冰箱的方法)
    • 现在增加一个公司C也生产电视和冰箱则符合开闭原则
      (新增一个C品牌的工厂方法)
    • 但如果是让原来的A和B新增加生产空调,则不符合开闭原则
      (原先工厂接口中没有生产空调的方法,需要修改代码)
  • 优点

    • 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。
    • 所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。
    • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。
    • 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
  • 缺点

    • 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
    • 开闭原则的倾斜性:增加新的工厂和产品族容易,增加新的产品等级结构麻烦
  • 适用场合:

    • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
    • 系统中有多于一个的产品族,而每次只使用其中某一产品族。
    • 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
    • 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
  • 抽象工厂模式包含四个角色:

    • 抽象工厂用于声明生成抽象产品的方法;
    • 具体工厂实现了抽象工厂声明的生成抽象产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中;
    • 抽象产品为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法;
    • 具体产品定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法。

实例

  1. 汽车保险管理程序
    在这里插入图片描述
    Insurance分类:Property,Com,BodyInjury,Collision,PersonInjury
    在这里插入图片描述
    (应为虚线箭头,依赖关系——消息关系)

  2. 房屋查询系统
    在这里插入图片描述
    在这里插入图片描述

BuildingFactory bf = BuildingFactory.getBuildingFactory(“option”);
if ( x ==House) House hs = bf.getHouse();
else if ( x ==Cando) Cando cd = bf.getCando();

类图

  • 简单工厂方法模式的设计类图如图2.5所示。图中的Creator类是一个工厂类,图中 的右半部分的类叫做产品类,由接口Product与具体的实现类产品类 ConcreteProduct组成。
    在这里插入图片描述
  • 工厂方法模式
    首先需要一个接口作为超类,名为creator,接口中有一个方法,叫做factory(); 然后可以用和产品类相同的结构产生创建者类结构,其中包含creatorA和creatorB, 各自负责创建相应的ProductA和ProductB的对象,如图2.6所示。在图2.6中,每个 产品类对应于一个工厂类。该工厂类只负责创建相应的产品类的对象。
    在这里插入图片描述
  • 抽象工厂模式
    抽象工厂模式(Abstract Factory Pattern)包含两个产品类的抽象工厂模式设计 类图如图2.11所示。
    在这里插入图片描述

习题

  • 课程作业
  1. 简答题
    在这里插入图片描述
    • 简单工厂模式一般不符合开闭原则。在简单工厂模式中,如果要增加一个新产品类,相应地在工厂类中也要增加一个条件语句,用于创建新的产品类的对象。也就是说,必须修改工厂类的源代码。
    • 工厂方法模式符合开闭原则,因为它通过定义一个抽象的工厂接口和多个具体的工厂类,每个具体工厂类都实现了工厂接口,用于创建一类产品。当需要新增一种产品时,只需要添加一个对应的具体工厂类,而不需要修改已有的代码。这样做的好处是,在不修改现有代码的前提下,可以灵活地扩展系统,符合开闭原则的要求。
    • 抽象工厂模式也符合开闭原则,它通过定义一个抽象的工厂接口和多个具体的工厂类,每个具体工厂类都负责创建一组相关的产品。当需要新增一组相关产品时,只需要添加对应的具体工厂类和产品类,而不需要修改已有的代码。这样可以保持系统的灵活性和可扩展性,符合开闭原则。
      • 倘若要是增加一组产品族产品,就不符合开闭原则,需要修改原有代码

开闭模式是指对扩展开放,对修改关闭,说的更通俗点,就是说开发了一个软件,应该可以对它进行功能扩展(开放),而在进行这些扩展的时候,不需要对原来的程序进行修改(关闭)。

  1. 在图2.17游戏软件设计中,游戏工厂(SlowGameFactory)类负责创建低级战士(SlowFighter)对象与低级怪物(SlowMonster)对象,并且将创建完的对象以其超类类型返回给用户界面客户端(ClientGUI)对象。然后,用户界面客户端(ClientGUI)对象将操纵低级战士(SlowFighter)对象与低级怪物(SlowMonster)对象,使得它们互相打斗。问题与任务:
    • 1、上述设计使用了什么设计模式?
    • 2、请在以上设计类图中添加4个新的类包括中级战士(MedFighter)、高级战士(SuperFighter)、中级怪物(MedMonster)和高级怪物(SuperMonster),使得中级战士(MedFighter)对象与中级怪物(MedMonster)对象是由一个抽象工厂类创建;高级战士(SuperFighter)对象与高级怪物(SuperMonster)对象由一个抽象工厂类创建,绘制新设计类图;
    • 3、除了以上添加的4个类以外,在以上类图中还应该添加什么类?
    • 4、描述新的设计类图;
    • 5、明确说明新设计的优点。

在这里插入图片描述
1. 上述设计使用了抽象工厂设计模式
2. 在这里插入图片描述
3. SuperGameFactory和MedGameFactory
4. 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。一个抽象工厂类,可以派生出多个具体工厂类。每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品
5. 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。

  • 案例分析

工厂模式案例
  使用工厂方法模式设计一个系统日志记录器,该系统日志记录器要求支持多种日志记录方式,如文件日志记录(FileLog)、数据库日志记录(DatabaseLog)等,每种记录方式均具有writeLog()方法记录数据,客户端(Client)可选择日志记录方式(logType)通过调用工厂类(LogFactory)中的createLog()方法创建日志(Log)对象,
  请按要求画出类图及其关键属性和操作。
  在这里插入图片描述

2.2 生成器模式

概念

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
生成器模式是一步一步创建一个复杂的对象

  • 优点

    • 在生成器模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
    • 每一个具体生成器都相对独立,而与其他的具体生成器无关,因此可以很方便地替换具体生成器或增加新的具体生成器,用户使用不同的具体生成器即可得到不同的产品对象。
    • 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
    • 增加新的具体生成器无须修改原有类库的代码,指挥者类针对抽象生成器类编程,系统扩展方便,符合“开闭原则”。
  • 缺点

    • 生成器模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用生成器模式,因此其使用范围受到一定的限制。
    • 如果产品的内部变化复杂,可能会导致需要定义很多具体生成器类来实现这种变化,导致系统变得很庞大。
  • 适用场景

    • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
    • 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
    • 对象的创建过程独立于创建该对象的类。在生成器模式中引入了指挥者类,将创建过程封装在指挥者类中,而不在生成器类中。
    • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
  • 生成器模式与抽象工厂模式

    • 与抽象工厂模式相比,生成器模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
    • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在生成器模式中,客户端可以不直接调用生成器的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。
    • 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么生成器模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

类图

在这里插入图片描述

Director director = new Director();
director.setBuilder(cb);
director.construct();
if ( cb == 1 ) Product1 = director.getObject();
else Product 2 = director.getObject();

  1. 确定Director,成员为xxxBuilder(表示要用到的生成器的父类),方法有设置生成器,获取产品(getXXX),创建整个产品的方法(这个方法用来调用生成器的各个方法,最后生成一个完整的产品)指挥者类针对抽象生成器类编程
  2. 确定XXXBuilder,成员为产品,方法就是构建这个产品所有部件所需的方法,以及getXXX获取产品,以及初始化产品的方法(肯定要先new申明一个空的产品,赋值给成员变量里的产品)
  3. 确定具体的生成器类,继承自XXXBuilder
  4. 确定具体产品,成员和方法就是该产品所需的各种方法属性等

实例

  • 某公司要设计一个房屋选购系统,系统内的房屋分为两种类型:普通型(NormalHouse)与豪华型(Luxury House)。不同房屋型的区别体现在面积(Area)大小以及卧室(Bedroom)、卫生间(Bathroom)、车库(Garage)、花园(Garden)和游泳池(Swimming Pool)的数量上。根据用户的选择,本软件采用三个图形界面,供用户具体选择房屋的各种指标。本程序采用生成器模式。
    • 在图形界面的左上部,有“Choose House Type”选择列表,在该选择列表中有Normal House 和 Luxury House选项。
    • Noumal House 界面中新增的部分包含 Area、Bedroom number、Garage type 和Garden type,每个选择都包含两个选项。
    • Luxury House 界面中的房屋属性部分包含 Area、Bedroom number、Garage type 、Garden type 和 Swimmingpool type,每个选择都包含两个选项。

时序图
在这里插入图片描述
只需要选择一个HouseBuilder,再调用构建的方法constructWholeHouse,再一个个调用生成部件的方法,调用设置参数方法,就能返回产品

类图
在这里插入图片描述

习题

  • 课程作业
    在例子的设计中,添加一个经济型房屋生成器类,命名为EconHouseBuilder。请绘制新设计的类图。
    在这里插入图片描述

  • 案例分析
    生成器模式案例
      使用生成器模式设计一款播放器软件主界面(MainScreen)类图。其中,主界面(MainScreen)有两种显示模式:完整模式(FullModeBuilder),精简模式(SimpleModeBuilder)。
      播放器由以下属性构成:显示菜单(Menu)、播放列表(PlayList)、主窗口(MenuWindow)、控制条(ControlBar)等。
      用户可以在选择界面上(MainGUI),通过系统管理(ScreenModeController),根据不同的显示类型(setModeBuilder),来定制具体的播放器的主界面对象(construct),并返回一个主界面(MainScreen)。
      其中,界面对象是通过调用build方法来对属性进行赋值的(例如,buildMenu方法)。两种类型的界面构建方式均实现了抽象类(ModeBuilder)。
      请按要求画出类图及其关键属性和操作。
    在这里插入图片描述
    (答案有误,关联关系应用消息关系替换)

2.3 单例模式

概念

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供一个全局访问点。

  • 动机
    在软件设计中,有时存在一个类仅能用来产生一个唯一对象的必要性,例如,大公司的打印室虽然可以有多台打印机,但是其打印管理系统中只有一个控制对象。

  • 解决方案

    • 在应用程序中使用代码保证仅有一个实例被创建
    • 类本身的结构确保其仅能够被创建一个实例
  • 要点:

    • 某个类只能有一个实例
    • 它必须自行创建这个实例
    • 它必须自行向整个系统提供这个实例
  • 优点

    1. 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
    2. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
    3. 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。
  • 缺点

    1. 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
    2. 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
    3. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。
  • 适用场景

    1. 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。
    2. 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
    3. 在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就需要对单例模式进行改进,使之成为多例模式。

类图

  • 将构造方法声明为private类型,其他类无法使用该构造方法来创建对象

  • 提供一个可以获得实例的getInstance方法,该方法是静态方法,确保客户程序得到的始终是同一个对象
    在这里插入图片描述

  • instance是静态变量,类型为Singleton,用于存储已经被创建的实例

  • getInstance()为实例创建方法,它创建实例的方式很特别:

    • 如果一个实例此前已经被创建了(因而被存储在变量instance中),则该方法返回instance;
    • 如果此前Single实例没有被创建,则该方法新创建并且返回Singleton类的一个实例,从而保证了实例的唯一性。
Public class Singleton {
	private static Singleton instance;
	private String name;
	private Singleton (String name) {
		this.name = name;
	}
	public static Singleton getInstance (String name) {
		if (instance == null && ! name.equals(null))
			instance = new Singleton (name);
		return instance;
	}
}

  • 多线程情况
    • 单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时有两个线程同时调用创建方法,那么就将导致两个线程各自创建了一个实例,从而违反了单例模式的实例唯一性的初衷
    • 为了改善以上的设计,可以将设计中的getInstance()方法声明为synchronized类型的。
      在这里插入图片描述

实例

在这里插入图片描述
在这里插入图片描述

习题

  • 课程作业
  1. 使用单例模式设计一个类图来表示随机数产生器(Randomer),整个应用程序中只需要一个类的实例来产生随机数(Random),客户端程序(Client)从类中获取这个实例,调用这个实例的方法有设置起始数方法setSeed()和随机产生下一个数方法nextInt()。请按要求画出类图及其关键属性和操作。
    在这里插入图片描述

  2. 用单例模式设计一个打印机管理软件(Spooler),每台计算机可以有若干个打印机(Printer)但只能有一个打印机管理软(Spooler),以避免两个打印作业同时输出到打印机中。客户端从类中获取Spooler的实例,调用这个实例的方法打印作业PrintJob(File job)。请按要求画出类图及其关键属性和操作。
    在这里插入图片描述

  • 案例分析
    单例模式案例
      为了节约系统资源,使用单例模式为联机射击游戏(ShooterGame)设计一个场景管理器(SceneManager),它包含一系列成员对象并可以使用manage()方法管理成员对象,此外还提供getInstance()方法用于创建场景管理器实例,instance用于存储已被创建的场景管理器实例,
    请按要求画出类图及其关键属性和操作。

3 结构型设计模式

  • 动机
    • 结构型软件设计模式的主要目的是将不同的类和对象组
      合在一起,形成更大或者更复杂的结构体
  • 内容
    • 组合模式将一个或者多个相似的对象构成组合对象
    • 适配器模式提供一种机制改变原来不适合使用的接口
    • 外观模式新建一个外观类,通过调用原有的类库中众多
      的类的方式,实现外观类的所有方法

3.1 组合模式

概念

  • 组合模式:组合多个对象形成树形结构以表示整体-部分 的结构层次组合模式对单个对象(即叶子对象) 和组合对象(即容器对象) 的使用具有一致性
    组合模式又可以称为“整体-部分”模式
关于组合模式的讨论
  1. 安全形式的组合模式

安全形式的组合模式:在Composite类中声明所有的用来管理子类对象的方法,包括add( )、remove( )以及getChild( )方法,而在Component接口和树叶类型的对象中不包含管理子类对象的方法。

优点:安全;
缺点:Component层次结构类的超类与组合子类的接口不一致。
在这里插入图片描述

  1. 透明形式的组合模式

透明形式的组合模式:在Component类中声明所有的用来管理子类对象的方法,包括add( )、remove( ),以及getChild( )方法。

优点:所有的构件类都有相同的接口;
缺点:不够安全。
在这里插入图片描述

优缺点
  • 优点:
    1. 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
    2. 客户端调用简单,客户端可以一致地使用组合结构或其中单个对象。
    3. 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
    4. 在组合体内加入对象构件更容易,客户端不必因为加入了新的对象构件而更改原有代码。

  • 缺点:
    1. 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联。
    2. 增加新构件时可能会产生一些问题,很难对容器中的构件类型进行限制。

  • 适用环境
    1. 需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。
    2. 让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。
    3. 对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们。

类图

  • 组合模式结构:
    • Component:为组合模式中的对象声明接口
    • Leaf:在组合模式中表示叶结点对象
    • Composite:表示组合部件
    • Client:通过Component接口操纵组合部件的对象。
      在这里插入图片描述
      (注意各个类中的固有方法,component和composite的关系是聚合)
      在这里插入图片描述

实例

  1. 五子棋游戏
    使用组合模式设计五子棋游戏。在五子棋中只有两种棋子:黑子与白子。
    使用抽象类棋ChessComponent给出五子棋的构件接口。该接口带有三个子类:BlackPiece、WhitePiece和Composite,其中前两个类封装了两种颜色的棋子,Composite类封装了ArrayList类型的聚合数据结构。
    每当玩家在棋盘(Board)上添加一个黑子或者白子,则相应地产生一个黑子或白子对象,然后将该对象存储到Composite对象中。其目的是在棋局结束以后,可以回放各个步骤
    在这里插入图片描述

五子棋游戏的设计类图如图3.4所示。在本类图中,还包含有用户图形界面类CLientGUI,该类调用另外一个棋盘类Board。棋盘类的功能包括生成棋盘图形、将棋子添加到棋盘上、重新开局与实现回放等。另外,还有两个辅助类,一个类是棋子图形类GChessPiece,其对象代表一个实际的棋子图像,该图像可以被添加到棋盘Board对象上;另外一个辅助类为GameOperations类,该类提供判断棋局结果的一些矩阵方法。

  1. 空军指挥系统
    使用组合模式设计空军指挥系统。空军力量(Airforce)包含战斗机(Fighter)、轰炸机(Bomber)、运输机(Transporter)与电子侦察机(EPlane)。
    战斗单位分为空军中队(Squadron)与空军(Group)各个战斗单位(AirUnit)可以由不同的机种组成。
    空军力量和战斗单位均有出击(fight)指令可执行。

习题

  • 课程作业
  1. 使用组合模式设计一个杀毒软件(AntiVirus)的框架,该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒,文件种类包括文本文件(TextFile)、图片文件(ImageFile)、视频文件(VideoFile)。请画出类图。
    在这里插入图片描述

  2. 如图所示是某公司的组织结构图,现采用组合设计模式来设计,其中公司(Company)为抽象类,具体公司(ConcreteCompany)表示具体的分公司或者办事处,人力资源部(HRDepartment)和财务部(FinanceDepartment)表示不同的部门,以上的类都拥有Add()方法和Delete()方法。请画出类图。
    在这里插入图片描述
    在这里插入图片描述
    其中Company为抽象类,定义了在组织结构图上添加(Add)和删除(remove)分公司/办事处或者部门的方法接口。类ConcreteCompany表示具体的分公司或者办事处,分公司或办事处下可以设置不同的部门。类HRDepartment和FinanceDepartment分别表示人力资源部和财务部。

  • 案例分析
    组合模式案例
    使用组合模式设计一个类图来表示水果店的物品。
      假设水果店有3种水果,分别是苹果(Apple)、西瓜(Watermelon)和橙子(Orange),另外,水果拼盘(FruitPlatter)是组合对象(Composite),拥有集合类型的fruitList私有成员变量,用于(动态地)添加水果到其数据结构中,所以可以产生由3种水果中的一种或多种组成的水果拼盘,当调用水果拼盘的获取价格操作时,将自动分别调用组合中的所有水果子类的获取价格的操作,得出水果拼盘的价格,而不需要知道水果拼盘的细节。
      三种水果和水果拼盘都继承于Fruit类,都有float类型的价格(price)私有成员变量,且都有添加(add)、删除(remove)和获取价格(getPrice)方法。
    请按要求画出类图及其关键属性和操作。
    在这里插入图片描述

3.2 适配器模式

概念概念

适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用

当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类

适配器可以使由于接口不兼容而不能交互的类可以一起工作

  • 适配器模式的作用:

    • 适配器模式是将接口不同而功能相近的两个接口加以转换,包括适配器角色补充一些源角色没有但目标接口需要的方法。
    • 但不要误以为适配器模式就是为了补充源角色没有的方法而准备的。
    • 适配器模式可以用于增加新的方法,但是,其主要意图是转换接口。
  • 在软件设计中,为了解决接口不一致的问题,两个软件模块之间往往也需要通过一个适配器类Adapter进行“适配”。这样的模式叫做适配器设计模式。该模式可以分为两种,分别为类适配器模式(Class Adapter Pattern)和对象适配器模式(Object Adapter Pattern)
    在这里插入图片描述

  • 类适配器模式与对象适配器模式的区别:

    • 在Java语言中,使用对象适配器模式可以把多种不同的源类都适配到同一个Target接口,而使用类的适配器模式是做不到这一点的。
    • 如果一个被适配源类中有大量的方法,使用类适配器模式比较容易,只需要让Adapter类继承被适配的源类即可。而此时使用对象适配器模式则要在Adapter类中明确写出Target角色中的每个方法,并且在每个方法中要调用被适配的源类中的相应的方法。
优缺点
  • 优点:

    • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
    • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
    • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
    • 由于类适配器是适配者类的子类,因此可以在类适配器中置换一些适配者的方法,使得适配器的灵活性更强。
    • 一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
  • 缺点:

    • 对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
    • 与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
  • 适用场合:

    • 系统需要使用现有的类,而这些类的接口不符合系统的需要。
    • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

类图

类适配器模式

在图3.19中,假如要使用类Adaptee中的方法Operation1,同时也要使用另外一个方法Operation2,而Operation2没有在类Adaptee中,怎样解决该问题呢?

  • 方案:用一个Target接口声明所有需要的方法,并且用另外一个Adapter类实现Target接口中所有的方法。同时,Adapter类继承Adaptee类,如果3.20所示。
    在这里插入图片描述
    Adapter中的Operation2方法是Adaptee中没有的
    实现箭头应该用虚线
对象适配器模式

在Java语言中不允许有多继承,所以,如果同时有两个或者两个以上的类Adaptee1、Adaptee2需要被适配,则不能够继续使用类适配器模式进行设计,图3.22所示的合计图是不合法的。

  • 在Adapter类中,采用聚合的办法来实现Operation1,这种方法即对象适配器模式,
    在这里插入图片描述
    在这里插入图片描述

实例

  1. 客户信息验证
    某公司购买了一个用于验证客户信息的离架产品类InfoValidation,但是卖方没有提供源代码。该类可以用于检查客户输入的信息,包含验证姓名、地址、电话区号和手机号码等功能。如果还需要增加一个验证社会安全号(SSN)的功能,则可以使用类适配器模式来实现。
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  1. 字符串排序
    使用对象适配器实现字符串序列排序。要求从一个.txt文件读入一些英文字符串,并且对这些字符串进行排序。这里已有一个类FileInput,其主要功能包含从一个文件中读入字符串;另外,在一个Java类库中有一个Arrays,其中包含功能sort,用于对多个字符串进行排序。
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

习题

  • 课程作业
  1. 现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法,类BinarySearch的binarySearch(int[], int)方法实现了二分查找算法。现使用适配器模式设计一个系统,在不修改源代码的情况下将类QuickSort和类BinarySearch的方法适配达到DataOperation接口中。请按要求绘制类图。
    在这里插入图片描述
    在这里插入图片描述

  2. 图3.26所示为一个用于整数排序的JAVA程序设计类图。该设计使用类适配器模式。
    – 请说明这个设计有什么问题?
    – 请画出新的设计类图。
    在这里插入图片描述
    Java是单继承语言,不支持多继承
    在这里插入图片描述

  • 案例分析
    某公司欲开发一款儿童玩具汽车(Car),为了更好地吸引小朋友的注意力,该玩具汽车在移动(move)过程中伴随着灯光闪烁(twinkle)和声音提示(sound),在该公司以往的产品(OldProduct)中已经实现了控制灯光闪烁和声音提示的程序,为了重用先前的代码并且使得汽车控制软件具有更好的灵活性和扩展性,使用适配器(CarAdapter)模式设计该系统,请按要求画出类图及其关键操作。
    在这里插入图片描述

3.3 外观模式

概念

为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这个子系统更加容易使用。

  • 外观模式由三个角色组成

    • 外观角色:外观模式的核心。它被客户角色调用,因此它熟悉子系统的功能。其内部根据客户角色已有的需求预定了几种功能组合。
    • 子系统角色:实现子系统的功能,对它而言,外观角色就和客户角色一样是未知的,它没有任何外观角色的信息和链接。
    • 客户角色:调用外观角色来完成要得到的功能。
  • 使用外观模式的目的:

    • 为一系列复杂的接口提供一个统一的接口,使该系统更容易使用。这个统一的接口可以被认为是更高级的接口,提供足够的功能,供客户程序直接调用,而复杂的对于类库的调用则被隐藏在该接口中,实际上由该接口负责调用。
  • 适配器模式外观模式都是为了设计新的接口而存在的,两种模式的不同之处在于:

    • 适配器模式转换接口的目的是将一个不适用的接口转换为可以被使用的接口,或将一些接口不同而功能相同或相近的接口加以转换,以便可以被统一使用
    • 外观模式简化接口是为了更好地使用某个类库。外观模式提供一个易使用的接口作为它的外观,只是为了使现有代码client和要使用到的类库(往往包含多个具有不同行为、不同接口的对象)通过这个新定义的Target接口能被更简单地使用。
  • 外观模式分析

    • 根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口
    • 外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度

一个统一的外观对象进行
客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道
外观模式的目的在于降低系统的复杂程度
外观模式从很大程度上提高了客户端使用的便捷性

类图

在这里插入图片描述

实例

在这里插入图片描述
在这里插入图片描述
active和deactive是各子系统类都有的方法;外观类中要声明各子系统类的参数
2.
在这里插入图片描述
在这里插入图片描述

习题

  • 课程作业

    1. 晚上睡觉之前,你总是喜欢看电视,在你进入卧室的时候你需要完成以下几个步骤:打开电灯light.turnOn()、打开空调air-conditioner.turnOn()、打开电视tv.turnOn()。通过这么些繁琐的步骤后你终于可以看电视了,但是你要睡觉了呢?又要去进行繁琐的关闭动作关闭电灯light.turnOff() 、关闭空调air-conditioner.turnOff() 、 关闭电视tv.turnOff() 。这里你就需要一个外观模式了,通过实现一个更加合理的接口外观类(DeviceFacade)将这些动作都包装起来,要求画出类图。
      在这里插入图片描述

    2. 某系统需要提供一个文件加密模块,加密流程包括三个操作,分别是读取源文件FileReader()、加密CipherMachine()、保存加密后的文件FileWriter()。读取文件和保存文件使用流来实现,这三个操作相对独立,其业务代码封装在三个不同的类中。现在需要提供一个统一的加密外观类(EncryptFacade),用户可以直接使用该加密外观类完成源文件(fileNameSrc)的读取、读取后的文件(plainStr)的加密和加密后文件(encryptStr)的保存三个操作,而不需要与每一个类进行交互,使用外观模式设计该加密模块,要求画出类图。
      在这里插入图片描述

  • 案例分析
    在金融机构中,当有客户(Client)前来抵押贷款mortgage()时,需通过抵押系统(Mortgage)对客户进行合格性验证isQualified(),只有验证通过后才能进行抵押贷款。抵押系统的合格性验证需要三个子系统同时工作:身份验证(Authentication)子系统确认客户身份是否合法isLegal()、信用(Credit)子系统查询其信用是否良好isCredible()以及贷款(Loan)子系统查询客户是否有贷款劣迹hasBadLoans()。只有这三个子系统都通过时才能通过合格性验证。使用外观模式模拟该过程,请按要求画出类图及其关键属性和操作。
    在这里插入图片描述

3.4 桥接模式

概念

  • 桥接模式:桥接模式是指将抽象部分与它的实现部分分离,使它们可以独立地变化。
  • 桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。
  • 动机:在所设计的程序中有几个维度时,将抽象部分与实现部分分离的设计,可以使程序独立、互不影响地增加类,而不需要改变甚至不需要重新编译已经存在的类。
优缺点
  • 优点:
    满足开闭原则
    1. 分离抽象接口及其实现部分。
    2. 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
    3. 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
    4. 实现细节对客户透明,可以对用户隐藏实现细节。
  • 缺点:
    1. 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
    2. 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
  • 使用环境
    • 需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系
    • 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响存在两个独立变化的维度
    • 不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统

类图

桥接模式是指将抽象部分与它的实现部分分离,使它们可以独立地变化。桥接模式的设计类图如图3.43所示。
在这里插入图片描述

实例

  1. 考虑一个自动茶水销售机的实例,该机器销售的茶水在杯子的体积上分为中杯和大杯,在茶叶品种上分为红茶和绿茶,这里要进行销售程序设计。为了简单起见,假设程序的主要功能为根据茶杯的大小与茶的种类计算一杯茶的销售价格。使用桥接模式进行设计。用户通过选择“Super Cup”或者”MediumCup”,及“Red Tea”或“Green Tea”获得一杯茶的价格。
    在这里插入图片描述

假设程序的主要功能为根据茶杯的大小与茶的种类计算一杯茶的销售价格,使用桥接模式进行设计。
在这里插入图片描述

  1. 在不同的度量下体积计算问题。假如要分别以米(Meter)与英尺(Foot)为单位来测量空间几何体立方体(Cube)和椭圆柱体(EllipseCylinder)的体积问题,其中立方体(Cube) 属性值有长(len)、宽(width)、高(height),椭圆柱体(EllipseCylinder) 的属性值有半长轴(aRadius)、半短轴(bRadius)、高(height),利用桥接模式进行设计。
    在这里插入图片描述

习题

  • 课程作业

    1. 开发一个跨平台视频播放器,可以在不同操作系统平台:WindowsVersion、LinuxVersion、UNIXVersion上播放多种格式的视频文件:MPEGFile、RMVBFile、AVIFile、WMVFile等。现使用桥接模式设计该播放器。
      在这里插入图片描述

    2. 现欲实现一个图像浏览系统,要求该系统能够显示BMP、JPEG和GIF三种格式的文件,并且能够在Windows和Linux两种操作系统上运行。系统首先将BMP、JPEG和GIF三种格式的文件,使用文件解析parseFile()方法解析为像素矩阵,然后使用doPaint()方法像素矩阵显示在屏幕上。系统必须具有较好的扩展性以支持新的文件格式和操作系统。为满足上述需求并减少所需生成的子类数目,采用桥接设计模式,请画出类图。
      在这里插入图片描述

  • 案例分析
    使用桥接模式设计不同品牌的不同家具的对象构造类图。宜家(YJ)、业通(YT)和百强(BQ)都是家具制造商,它们都生产沙(sofa)、茶几(TeaTable)和电脑桌(ComputerTable)。现需要设计一个系统,描述这些家具制造商以及它们所制造的家具,其中家具制造商( FurnitureProducer)具有生产方法produce()和设置家具类型方法setFurniture(Furniturefurniture),家具类(Furniture)有装配方法assemble(),请按要求画出类图及其关键属性和操作。
    在这里插入图片描述

4 行为型设计模式

  • 动机
    • 行为型软件设计模式关心算法和对象之间的责任分配,不仅是描述对象或类模式,更加侧重描述它们之间的通信模式
  • 内容
    • 迭代器模式抽象了访问和遍历一个集合中的对象的方式
    • 访问者模式封装了分布于多个类之间的行为
    • 中介者模式通过在对象间引入一个中介对象,避免对象间的显式引用
    • 策略模式将算法封装在对象中,这样可以方便指定或改变一个对象使用的算法
    • 状态模式封装了兑现过的状态,使得当对象的状态发生变化时,该对象可以改变自身的行为

4.1 迭代器模式

概念

  • 迭代器模式的关键思想是将对列表的访问和遍历从列表对象中分离出来,放入一个独立的迭代对象中
  • 迭代器模式能够提供一种方法按照顺序访问一个聚合对象元素中的所有元素而又不需要暴露该对象的内部表示
优缺点
  • 迭代器模式的优点

    • 迭代器模式支持以不同的方式遍历同一个聚合,复杂的聚合可用多种方式进行遍历。例如,二叉树遍历方法有四种:前序遍历、中序遍历、后序遍历和层次遍历。可以将不同的遍历算法封装在不同的迭代器子类中,每个迭代器保持自己的遍历状态,因此可以同时进行多种不同方法的遍历。
    • 满足开闭原则
      • 当修改某一个遍历算法时不会影响其他的遍历算法。
      • 当修改遍历的聚合的结构代码时,如果该聚合的结构没有改变,则相应的遍历算法代码也不需要改变。
    • 迭代器简化了聚合的接口。有了迭代器的遍历接口,聚合本身就不需要类似的遍历接口了,这样就简化了聚合的接口。
  • 缺点:

    • 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类
    • 类的个数成对增加,这在一定程度上增加了系统的复杂性。
  • 模式适用环境
    在以下情况下可以使用迭代器模式:

    1. 访问一个聚合对象的内容而无须暴露它的内部表示。
    2. 需要为聚合对象提供多种遍历方式。
    3. 为遍历不同的聚合结构提供一个统一的接口。

类图

在这里插入图片描述
Aggregate聚合接口,其实现子类将创建并且维持一个一种数据类型的聚合体。另外,它还定义了创建相应迭代器对象的接口creaIterator。

ConcreteAggregate封装了一个数据存储结构,实现一个具体的聚合,如列表、java类型ArrayList等。一个聚合对象包含一些其他的对象。另外,该类提供了创建相应迭代器对象的方法createIterator,该方法返回类型为ConcreteIterator的一个对象。

Iterator迭代器定义访问和遍历元素的接口

ConcreteIterator具体迭代器实现迭代器接口,对该聚合遍历时跟踪当前位置。

实例

在这里插入图片描述
在这里插入图片描述
迭代器里的四个方法(一般)
1.boolean hasNext()
2.next()
3.remove()
4.getNumOfItems()

在这里插入图片描述
在这里插入图片描述

习题

  • 课程作业
  1. 在例4.1中增加一个迭代器,按照斜对角线迭代遍历矩阵。请画出类图
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 案例分析
    使用迭代器模式设计一个类图来表示对整数矩阵(Matrix)进行不同方式的遍历。其中遍历方式有:按照奇数类型进行遍历(OddIterator)和按照偶数类型进行遍历(EvenIterator)。整数矩阵(Matrix)可以获得该矩阵的整数(getMatrixData);可以选择不同的迭代器进行遍历(createOddIterator和createEvenIterator)。其中,两种迭代器均实现了抽象类NumberIterator的操作方法(hasnext、next、remove、getNumOfItems)。
      请按要求画出类图及其关键属性和操作。
    在这里插入图片描述
    在这里插入图片描述

4.2 访问者模式

概念

  • 访问者模式是指作用于一个对象结构体上的元素的操作。访问者可以使用户在不改变该结构体中的类的基础上定义一个新操作。

  • 特征

    1. 对于系统中的某些对象,它们存储在同一个集合中,具有不同的类型
    2. 对于该集合中的对象,可以接受一类被称为访问者的对象来访问
    3. 不同的访问者其访问方式有所不同
  • 目的:
    封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变

  • 模式动机:
    为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式

  • 使用访问者模式的情况:

    1. 当一个对象的结构中,包含有多种类型的具有不同接口的对象,且用户要在这些对象上进行依赖于具体的类的运算时,需要用到访问者模式;
    2. 当有多个不同的并且互不相关的运算将作用到这些对象上,而用户不希望这些运算混淆这些类时,可以使用访问者模式将相关的操作放到一个独立的类中;
    3. 在对象的数据类型很少改变,但是需要经常改变操作或者增加新操作的情况下,可以使用访问者模式。
  • 优点:

    • 使得在访问者类中针对复杂类结构中的某个类添加新方法较为容易,即只需要简单地添加一个新的访问者方法即可。
    • 访问者将相关的方法集中在一个具体的访问者类中,而将其他相关的方法集中在另外一个具体的访问者类中。
  • 缺点:

    • 增加一个具体的新ConcreteElement类比较困难。

类图

  • 访问者模式是指作用于一个对象结构体上的元素的操作。访问者可以使用户在不改变该结构体中的类的基础上定义一个新操作。访问者模式的类图如下。
  • 该类图包含两个系列的类:Element类,访问者类。访问者类定义了施加于Element类上的操作,为Element类提供一些功能。
  • 需要定义一个访问者父类Visitor以及用于各种特殊目的具体的子类。Visitor必须为每个结点类提供一个操作,即访问方法

首先分为两个部分,一是元素类,而是访问者类
先抽象再具体
在访问者类里要有访问元素的方法visitXXX(抽象元素父类做参数)之类的
在元素类里要有接受访问的方法,比如accept(抽象访问者父类做参数)等

在这里插入图片描述

  • 访问者与被访问者的关联
    • 访问者层次类Visitor与被访问者层次类Element存在关联,并且是双向的关联。
    • Element->Visitor:由接受方法accept( Visitor v) 的参数产生;
    • Visitor->Element:由访问者方法visitElementA( ElementA ) 与visitElementB(ElementB )的参数产生。
      在这里插入图片描述

实例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在客户程序中将直接创建运动鞋子类与访问者子类的对象,然后直接调用运动鞋子类的 accept 方法。根据用户输入的所购买鞋的数量和单价来计算总价和获得相应特点的功能分别由 Visitor 类的两个子类 PriceVisitor 和 ShoeInfoVisitor 来实现。访问者类图如下。

在这里插入图片描述
在这里插入图片描述

习题

  • 课程作业
  1. 在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  2. 在这里插入图片描述
    在这里插入图片描述

  • 案例分析
    使用访问者模式设计高校奖励审批系统。某高校奖励审批系统可实现教师奖励的审批和学生奖励的审批,可以参与评选的人员(Person)有教师(Teacher)和 学 生 ( Student ) , 审 批 的 奖 励 项 目 ( AwardCheck ) 包 括 科 研 奖(ScientificAwardCheck)和成绩优秀奖(ExcellenceAwardCheck)。其中,教师或学生的论(paperAmount)超过规定数目可评选科研奖,教师教学反馈分(feedbackScore)或学生平均分(score)达到规定分数可评选成绩优秀奖。系统中的候选人列表类(CandidateList)定义了一个人员列表(PersonList),用于存放待审核的教师和学生信息,并可以对列表进行添加
    人员(addPerson)、删除人员(removePerson)和审批人(accept)等操作。客户端(Client)创建候选人列表对象,通过调用候选人列表类的方法,允许奖励审批对象(AwardCheck)访问参与评选的人员(Person)。请按要求画出类图及其关键属性和操作。
    在这里插入图片描述

4.4 中介者模式

概念

  • 模式动机:
    为了减少对象两之间复杂的引用关系,使之成为一个松耦合的系统,需要适用中介者模式

  • 定义:
    用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使耦合松散,而且可以独立地该变它们之间的交互

  • 中介者模式又称为调停者模式,它是一种对象行为型模式

  • 中介者模式的要点是将所有对象之间的交互细节抽象到一个独立的类中,这个类叫做中介者类Mediator,如图4.35所示。每个对象仍然负责提供原来设计的服务,但是对象之间不再有直接交互,对象之间的交互经由Mediator类完成。
    在这里插入图片描述

  • 中介者模式的优点:

    1. 所有对象的交互行为都被转入到一个独立的中介者对象中,使得用户更容易通过中介者修改对象之间的相互关系行为。当修改一个交互行为的时候,可以通过修改或者更换其中一个具体的中介者子类来完成。另外,将对象的交互转移到一个专门的类中也提高了对象的可重用性。
    2. 因为对象之间不直接有交互,使对象的单元测试更加容易。
    3. 低耦合使得一个类的修改不会影响到其他的类。
  • 缺点:

    • 在具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护
  • 适用场合

    1. 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
    2. 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。
    3. 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象交互的公共行为,如果需要改变行为则可以增加新的中介者类
  • 中介者模式与迪米特法则
    在中介者模式中,通过创造出一个中介者对象,将系统中有关的对象所引用的其他对象数目减少到最少,使得一个对象与其同事之间的相互作用被这个对象与中介者对象之间的相互作用所取代。因此,中介者模式就是迪米特法则的一个典型应用

类图

在这里插入图片描述

实例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

习题

  • 课程作业
  1. 使用中介者模式来说明联合国的作用。将联合国(UnitedNations)定义为抽象中介者类,联合国下属机构如WTO等作为具体中介者类,能够设置国家如setChina、提供了发消息和收消息的方法(declare、getMessage);国家(Country)作为抽象参与者类,而将中国(China)、美国(America)作为具体参与者类,实现了发消息和收消息的功能( declare、getMessage )。要求绘制相应的类图。
    在这里插入图片描述

  2. 某公司欲开发一套窗体图形界面类库。该类库需要包含若干预定义的窗格(Pane)对象,窗格之间的操作有修改和升级(modify、update );窗格包括文本窗格(TextPane )、列表窗格(ListPane )、图形窗格(GraphicPane )三种具体对象,窗格之间不允许直接引用。基于该类库的应用由一个包含一组窗格的窗口(Window)作为抽象中介类,具体窗口(ConcreteWindow)作为具体中介类,实现action方法协调窗格之间的操作。在相互之间不直接引用的前提下需要实现窗格之间的协作,现采用中介者模式设计该系统,请绘制出类图。
    在这里插入图片描述

  • 案例分析
    使用中介者模式设计房屋租赁系统。房屋中介(HouseMediator)作为具体中介者类,实现了中介者接口类(Mediator)的方法,能够向参与者发送房产信息(sendMessage),并提供了参与者的注册方法(registerLandlord与registerRenter);房东(Landlord)和房客(Renter)作为具体参与者类,实现了参与者接口类(Person)的方法,能够向房屋中介发送需求(sendMessage)及从房屋中介获取房产信息(getMessage)。请按要求画出类图及其关键属性和操作。
    在这里插入图片描述
    注意几个参数、构造方法

4.5 策略模式

概念

  • 面向对象程序设计遵循的一个基本原则是责任分离,另一个则是高内聚和低耦合

  • 策略模式定义了一系列算法,将每一个算法封装起来,并且使它们之间可以相互替换。策略模式让算法的变化不会影响到使用算法的客户

  • Context类的作用:

    • 作为客户类(Client)和策略类(Strategy)的“传话筒”,将客户类的请求与由客户类所提供的各种参数传递给策略类。客户类通常创建并且传递一个ConcreteStrategy的对象给Context。然后客户类即可和Context类单独交互;
    • 可以将算法所需要的所有数据传递给策略类;
    • 可以将自己以参数的形式传递给策略的运算,然后当需要时,让策略类调用Context。
  • 使用策略模式的情况:

    1. 当有多个仅在行为上不同但是相关的类存在时,策略模式提供了一个为一个类配置多种行为之一的方法;
    2. 当一个算法使用用户不应该知道的数据时,使用策略模式可以将算法实现细节隐藏起来,避免暴露于算法相关的复杂细节。注意:虽然可以将算法实现细节封装起来,但是客户程序必须知道各个策略子类的接口;
    3. 当一个类有多种行为,这些行为以大块的条件语句实现时可以使用策略模式,这时可以将条件块移入它们自己的Strategy类。
  • 优点:

    • 得到一系列可以复用的算法,这些算法继承一个共同的抽象类,因此共有的功能可以放到超类中;
    • 将不同算法封装在不同的策略子类中,使逻辑更加清晰,各个算法可以独立地变化;
    • 使功能改变或者扩展更容易,修改一个算法不必重新编译“Client”与
      “Context”。
  • 缺点:

    • 客户程序必须知道不同策略接口的各个子类的行为,必须理解每个子类有哪些不同。因此,在客户类中通常存在许多与策略类各个分支相关的条件语句,用于选择产生策略子类对象,然后将这些对象传递给Context类,而Context类则直接使用此对象调用策略模式的策略子类的方法。
  • 可扩展性讨论:

    • 客户类负责创建策略子类对象的情况:客户类通常包含与策略相关的条件语句,而在Context类中不必使用任何与策略有关的条件语句。因此修改或添加一个策略子类都不必修改Context类。但是在添加一个新策略子类的情况下,如果客户类需要使用该子类,往往需要在客户类中添加一个新的条件语句。
    • Context类负责创建策略子类对象的情况:Context类在创建策略子类对象时,必然会使用与策略子类有关的条件语句。此时,修改一个策略子类不需要修改客户类与Context类。而在添加一个新的策略子类时,如果此时客户类暂时不使用新的子类,则新子类的添加不会影响客户类和Context类的源代码。但是如果客户类需要使用新子类,则必须同时在客户类与Context类中添加新的条件分支。
    • 综上所述,由客户类创建对象的设计可扩展性好一些。这样可以做到在Context类中不出现与策略子类相关的条件语句,从而可扩展也得到了提高。

类图

Strategy:定义了一个共同的接口,所有具体的算法类实现这个接口。环境(上下文)类Context使用这个接口调用具体的算法类。

ConcreteStrategy:封装了具体的算法,实现同一个接口。

Context:环境(上下文)类。用于配置一个具体的算法策略对象,维持一个策略接口类型的参考(Reference),并且可以定义一些让接口
Strategy的具体对象访问的接口。在简单情况下,Context类可以省略。
在这里插入图片描述

实例

在这里插入图片描述
在这里插入图片描述
StrategyGUI:用户图形界面,便于用户输入被排序的整数序列,然后
选择算法。

Context:将算法所需要的所有数据传递给策略接口类SortAlgorithm。

SortAlgorithm:被4个具体的排序算法类实现。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

习题

  • 课程作业
  1. 某电影院售票系统(MovieTicket)为不同类型的用户提供了不同的打折方式(Discount),学生凭学生证可享受8折优惠(StudentDiscount),儿童可享受减免10元的优惠(ChildrenDiscount),VIP用户除享受半价优惠外还可以进行积分(VIPDiscount),不同的用户实现了不同的计算方法(calculate)。售票系统可定义价格(price)和折扣(discount)两个属性,同时拥有设置价格(setPrice),设置折扣(setDiscount)、获取价格(getPrice)的方法,使用策略模式设计该系统,绘制相应的类图。
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 案例分析
    现有一种玩具,它可以模拟三种动物(Animal)的叫声:狗叫(Dog)、猫叫(Cat)和青蛙叫(Frog),每一种动物都有cry()方法用于实现发出叫声的操作。玩具类(Toy) 使用setAnimal()方法设置动物,并使用属性currentAnimal记录玩具当前模拟的动物,使用 run()方法来调用动物的cry()方法使玩具发出叫声。为将来能模拟更多的动物,采用策略模式设计该系统,请按要求画出类图及其关键属性和操作。
    在这里插入图片描述

4.6 状态模式

概念

  • 状态模式可以有效地消除在客户程序中的条件语句,并且使得状态转换非常清楚。状态模式将不同状态下的行为封装在一个层次类的不同子类中。

  • 在下列情况下可以使用状态模式。

    1. 当对象的行为依赖于状态,对象要在运行过程中改变状态时使用状态模式。
    2. 当操作带有大量依赖于状态的条件语句时使用该模式。通常许多操作都含有相同的条件结构,状态模式将条件结构的每个分支包装成独立分支的类,使得在不同状态下的行为可以独立变化。
  • 关于context类的说明如下

    • context类代表状态相关的与现有concreteState有关的请求。
    • context类可以将自己作为一个参数传递给状态对象,以便在需要时状态类可以回访context类。
    • context类是client类的主要接口,client类可以用State的对象配置context类。当配置好之后client类不必再与State类直接交互,与State类的交互由context类完成。
    • context或者State超类或者子类都可以决定状态的顺序。
  • 策略模式和状态模式的相似之处
      两种模式在结构上是相同的。策略模式将每个条件分支封装在一个子类中,而状态模式将每个状态封装在一个子类中。

  • 策略模式和状态模式的区别

    • 策略模式用来处理一组具有相同目的但是实现方法不同的算法,这些算法方案之间一般来说没有状态变迁,并且用户总是从几个算法中间选取一个。
    • 状态模式则不同,它实现的一个概念可以叫做动态继承,也就是继承的子类都可以发生变化。状态的变化可以由一个状态迁徙图表示。

类图

在这里插入图片描述
Context:定义了与客户程序的接口,它保持了一个concreteState的代表现在状态的实例。

State:定义了状态接口,它的各个子类封装了在各种不同状态下的行为。

ConcreteState子类:封装了在各种不同状态下的行为。

实例

在这里插入图片描述
在这里插入图片描述
纠正
在这里插入图片描述

在这里插入图片描述

习题

  • 课程作业
  1. 某纸牌游戏软件中,人物角色状态(RoleState)具有4种,分别是入门级状态(PrimaryState)、熟练级状态(SecondaryState)、高手级状态(ProfessionalState)和骨灰级状态(FinalState)。人物角色状态拥有自己的角色属性(role),分数属性(point)、等级属性(grade)和检查方法(check),角色的等级与其积分相对应、游戏胜利将增加积分,失败则扣除积分。入门级具有基本的游戏功能play(),熟练级增加了游戏胜利积分加倍功能doublescore(),高手级在熟练级基础上再增加换牌功能changecards(),骨灰级在高手级基础上再增加偷看他人的牌功能peekcared()。现在使用状态模式来设计该系统,绘制类图。
    在这里插入图片描述

  2. 在射击游戏中,游戏角色(Player)存在几种不同的状态(State)和名字属性(name),分别为正常状态(NomalState)、暂停状态(PauseState)、阵亡状态(DeathState)。在不同状态下角色对象的行为不同,行为有暂停(pause)、开始(start)、被射击(beAttacked)、射击(shot)、移动(move)。使用状态模式来设计和实现角色状态的转换。
    在这里插入图片描述

  • 案例分析
    使用状态模式设计一个类图来表示火箭升空的状态改变。其中火箭升空的大致状态有:系统启动状态(StartState)、加速状态(AccelerateState)、稳定运行状态(StableState)。环境类(Context)通过执行doAction()来改变火箭升空过程中的状态改变。其中三个升空状态类均继承父类RocketState状态类,能够创建升空状态对象(createStateObj),实现了升空状态变化逻辑(changeState),不同升空状态子类分别实现了当前状态下的操作(performTask) 。当火箭运行到稳定状态时候,环境类(Context)一直保持这个状态。请按要求画出类图及其关键属性和操作。
    在这里插入图片描述

体系结构

  • 概念:软件体系结构是系统的基本组织结构,包括系统
    构成要素、这些构成要素相互之间以及与运行环境之间
    的关系,还包括系统设计及演化时应遵循的原则。
  • 意义:
    • 软件体系结构是早期关键设计决策的体现。
    • 软件体系结构是软件相关人员之间进行交流的手段。
    • 软件体系结构是一种高层次的设计复用手段。

6.1 调用-返回风格软件体系结构

概念

  • 调用-返回风格软件体系结构的概念

    • 利用调用-返回风格软件体系结构设计的软件系统使用的是分而治之策略,其主要思想是将一个复杂的大系统分解为一些子系统,以便降低复杂度,并且增加可修改性。这种系统的程序执行顺序通常只由一个单线程控制。
    • 每个软件构件都设计为有一个唯一的程序执行起点和一个唯一的程序执行终点;程序从其执行起点开始执行该构件的代码,程序执行结束,将控制返回给程序调用构件,其中,程序构件通常叫做子程序,从一个构件到另外一个构件的控制传递叫做子程序调用。
      在这里插入图片描述
  • 主程序-子程序软件体系结构

    • 主程序-子程序软件体系结构在设计上使用层次化的划分方法,该体系结构中使用由编程语言直接支持的单一的控制线程。

    • 子程序的结构是明确的,子程序通常组成程序模块。子程序的调用呈现层次状,其正确与否往往取决于其调用的子程序的正确与否。
      在这里插入图片描述

    • 自顶向下的设计方法的问题
      功能演化困难
      现实中的系统功能不容易描述
      功能化设计丢掉了数据与数据结构
      由功能设计得到的软件产品的可复用的代码较少

    • 结构化设计的优缺点
      优点

      • 逻辑设计与物理设计分离
      • 开发过程中形成一套规范化的文档,便于将来修改和维护
        缺点
      • 开发周期长,开发过程复杂
      • 系统难于适应环境变化
      • 经验表明,较小的程序(小于10万行)适合于结构化开发
  • 面向对象体系结构
    在这里插入图片描述

    • 面向对象体系结构
      封装性
      继承性
      多态

    • 面向对象设计的优缺点
      优点

      • 容易维护
      • 可复用性好
      • 映射现实世界
      • 容易对一个系统进行剖分
    • 缺点

      • 面向对象程序占用内存较大
      • 一个对象要和另外一个对象交互,该对象必须知道另外一个对象的身份,包括对象名、方法名和参数类型等。
  • 主程序-子程序与面向对象体系结构的比较

    • 相似之处
      两种设计都属于调用– 返回风格。
      (1)主程序-子程序体系结构所产生的程序中,每个软件构件都有一个唯一的程序执行入口和唯一的程序执行出口,并且当一个构件调用另外一个构件时,程序运行控制权将转移给被调用的构件,当被调用构件的代码执行完毕以后,即程序运行至该构件的出口以后,控制被返回给调用的控件。
      (2)由面向对象体系结构所产生的程序中,当一个对象的方法调用另外一个对象的方法时,被调用对象将接过程序运行控制,当被调用对象的方法运行完以后,控制将交还给调用对象。
    • 设计方法的区别
      • 结构化设计:
        关注功能,过程;
        复用性 低;
        定义非常明确的项目,稳定的用户需求;
        结构图 元素:图中结点都是函数;
        数据与操作是分离的;数据外露,所有的方法均可以访问数据
      • 面向对象设计
        关注对象,数据;
        复用性 高;
        需求可能经常改变的大型项目;
        程序设计类图 元素:图中每个结点都是类;
        类中封装了数据与可以施加于该数据上的操作。

结构图

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

案例

在这里插入图片描述
遗留文件更新系统的结构化设计。
• 包含一个主程序与其他5个函数,Input负责输入整个文件,CorrectPolitical-Error负责修改政治错误,CorrectY2KError负责修改两千年问题,Sort负责排序,Output负责将处理好的文件输出到一个文件中。
在这里插入图片描述
遗产文件更新系统的面向对象设计。
• 在本设计中,将输入、政治问题、排序、输出功能封装在4个类中,让它们实现一个统一的接口FileUpdateInterface。每个类都实现一个update()方法,以便实现以上的相应功能。
• 本设计中,所有的数据都被封装在各个类中,每当创建一个类的对象以后,内存都会为该对象开辟出一片内存空间。
在这里插入图片描述

习题

  • 课堂练习
  1. 主程序-子程序体系结构与面向对象体系结构各自使用什么设计方法?两种程序设计最终得到什么图?
    结构化设计、面向对象设计;结构图
  2. 试用主程序-子程序结构设计程序:从键盘输入一个2位十进制的月份数(01-12),然后显示出相应的英文缩写名。请画出程序结构图。
    在这里插入图片描述
  • 案例分析
    试用主程序- 子程序软件体系结构设计一个员工信息查询系统(Staff Information Query System),要求输入员工工号(Staff Number),输出该员工的信息(Staff Information),包括姓名(Name),部门(Department),月薪(Salary),联系电话(Phone)等。请按要求画出数据流图和程序结构图。
    在这里插入图片描述

6.2 数据流风格软件体系结构 (管道-过滤器)

概念

  • 数据流风格三种例子
    Batch Sequential(批处理)
    Pipe-and-Filter(管道-过滤器)
    Process Control(控制)

  • 顺序批处理软件体系结构

    • 在该系统中,组件为独立的程序,并且这些组件按照先后顺序处理,即只有当一个组件的运行彻底结束以后,下一个组件才能开始执行。可以认为,数据在处理步骤之间的传输是成批(块)的,而不是以数据流的方式进行的。这也是"顺序批处理"名称的由来。
    • 批处理系统特点
      • 每个处理程序模块都是互为独立的程序
      • 只有上一步程序彻底完成,下一步程序才能开始
      • 数据作为一个整体进行传输
      • 因为以上的特点,所以不必对其组件进行同步处理
      • 因为几个组件只能按照顺序运行,而不能同步运行,所以性能可能比那些能按照几个组件同时运行的程序要差一些
      • 使用顺序批处理结构设计的软件不适用于要求对数据进行实时处理的系统
  • 管道-过滤器软件体系结构

    • 管道的三个组件
      Input Stream 输入流
      Pipe 管道
      Output Stream 输出流

    • 作用:在过滤器之间传送数据
      单向流
      可能具有缓冲区
      管道形成传输图
      不同的管道中流动的数据流,具有不同的数据格式
      原因:数据在流过每一个过滤器时,被过滤器进行了丰富、精炼、转换、融合、分解等操作,因而发生了变化

    • 管道-过滤器软件体系优点

      • 并发性:对于海量数据处理问题,可以提高高通量的产出
      • 可复用性:封装了过滤器,使得过滤器可以被非常容易地插入与替换
    • 将每个过滤器的输入/输出限制为单一的,则管道-过滤器退化为顺序批处理系统(Batch sequential)

    • 管道过滤器构成的网络,其输出的正确性与过滤器的递增处理顺序无关

  • 顺序批处理系统与管道-过滤器软件体系结构的比较

    • 两种体系结构的相似点在于:
      • 处理模块的互相独立性。
        在批处理系统与管道-过滤器体系结构中,处理过程是互相独立的,即在批处理系统体系结构中,每个处理过程都不调用其他处理过程;在管道-过滤器体系结构中,每个过滤器都不调用其他过滤器。
    • 两种体系结构的不同点在于:
      • 数据处理方式不同。
        在顺序批处理系统中,只有上一步程序彻底完成了,下一步程序才能开始进行,数据作为一个块状整体传输。通常是每个中间步骤都要有中间存储。各个处理过程不是同时工作,只有前面相邻的处理过程完成时,后面的过程才能开始工作。在管道-过滤器体系结构中,过滤器渐进式地以流对流的方式变换数据,各个过滤器在同时工作。
  • 控制流

类图

在这里插入图片描述
在这里插入图片描述

案例

  • 顺序批处理软件体系结构
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 管道-过滤器软件体系结构
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

习题

  • 课堂练习
  1. 下列两种情形中哪种适合使用顺序批处理体系结构
    a、一个需要对数据进行实时处理的银行业务系统。
    b、一个需要周期性更新的人口普查系统。
    答案:b
  2. 使用管道-过滤器体系结构设计与实现一个处理职员收入信息的软件。程序应该按照如下方式对输入文件employee.txt进行处理:(1)按照职员工资金额对文件内容进行排序。(2)找出年薪在120000元以上的所有职员。(3)计算年薪在120000元以上的职员的个人所得税,税率为30%。(4)打印出年薪在120000以上的职员的税收信息。
    a、画出设计的(管道图)逻辑图
    b、画出设计类图,包括所有的类的设计与方法。
    c、描述每个方法的功能。

在这里插入图片描述
继承箭头改为实现箭头
Filter类:setInPipe()和setOutPipe()负责设置输入输出管道,Start()和Stop()负责控制过滤器开始工作和暂停工作;
Filter类的子类:processData()负责实现每个子类对应的功能;
Pipe类:read()和write()实现数据的读取与写入,close()实现流的关闭。

  1. 使用管道-过滤器体系结构设计以下问题。要求:在设计的在线销售系统中,新的订单以消息的形式送达企业。该消息被加密,包含数字证书形式的认证信息,并 且可能有重复发送消息的情况发生。系统功能包括:(1)输入客户订单消息。(2)对该消息进行解密。(3)检查认证信息。(4)检查是否有重复发送的消息,如果被复制 的消息发生了,则取消重复的订单,仅保留一份订单。要求:
    a、画出设计的(管道图)逻辑图
    b、画出设计类图,包括所有的类的设计与方法。
    c、描述每个方法的功能。
    在这里插入图片描述
    继承箭头改为实现箭头
    c.
    Filter类:setInPipe()和setOutPipe()负责设置输入输出管道,Start()和Stop()负责控制过滤器开始工作和暂停工作;
    Filter类的子类:processData()负责实现每个子类对应的功能;
    Pipe类:read()和write()实现数据的读取与写入,close()实现流的关闭。
  • 案例分析
    使用管道-过滤器体系结构设计模拟C程序代码source.cpp的执行过程:
    1)编译(Compiler):将source.cpp翻译成目标代码(compile()),目标代码是在目标机器上运行的代码。
    2)链接(Linker): 将目标代码与C函数库相连接(link()),并将源程序所用的库代码与目标代码合并,并形成最终可执行的二进制机器代码。
    3)执行(Execution): 在特定的机器环境下运行(execute())C程序。要求画出设计类图,包括所有的类的设计与方法。
    在这里插入图片描述

6.3 事件系统软件体系结构 (重点:观察者模式)

概念

  • 显示调用: 如果一个系统的组件提供了一组子程序或者方法,其中的一个子程序调用其他子程序,并准确知道该子程序的名字,包括参数与返回值等,则说这种调用是显示调用。

  • 隐式调用: 隐式调用将调用者与被调用者解耦,调用者可以不知道谁是被调用者。隐式调用中的事件发布模块对象只是将事件广播
    到事件空间,而不知道哪些对象被调用了,也不知道调用以后的后果如何。事实上,在实际应用中,对某种事件感兴趣的组件可以
    注册该类事件,而当该事件被广播以后,运行该组件的事件处理方法(过程)。

  • 事件系统软件体系结构: 事件系统中的组件的注册、广播、调用机制如下:
    (1)系统中的每个组件可以注册一种或多种事件(对该事件感兴趣)。
    (2)当一个组件要发布事件时,该组件可以广播一个或者多个事件到事件空间。
    (3)每当一个事件被广播了,系统将负责自动调用那些已经注册了该事件(对该事件感兴趣)的组件或者过程,被调用的组件(对象、过程)将运行。事件发布组件不知道被调用的组件将要做什么。

类图

观察者模式的各组成部分说明如下:
(1)Observable:被观察者接口,声明了三个应该实现的方法。在简单的情况下,register(obs:Observer)方法负责将参数中的观察者注册到Subject对象在Subject对象中保持一个具体的观察者列表,用于记载所有的观察者。unRegister(obs:Observer)方法用于在列表中删除参数中的观察者对象,该方法通常可以被省略。Notify()方法用于通知观察者subject状态的改变

(2)Subject:具体的观察者要依赖的对象,它要实现Observable的所有方法。在Subject中的getState()方法可以被ConcreteObserver调用,以便得到最新的状态。

(3)Observer:观察者接口,代表依赖对象。观察者可以有多个。

(4)ConcreteObserver:代表具体的观察者对象。
在这里插入图片描述

案例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

习题

  • 课堂练习
  1. 在例6.19的设计与实现中,当调用Observable类的notifyObserver之前,还应该同时调用什么方法?
  • setChanged方法是Observable类的一个方法,用于设置对象的状态已经改变。它会将Observable对象的changed字段设置为true,表示对象的状态已经发生了改变。
  • 在观察者模式中,当被观察对象的状态发生改变时,需要先调用setChanged方法来标记状态已经改变,然后再调用notifyObserver方法通知所有观察者。这样做的目的是确保只有当状态发生改变时才会通知观察者,避免不必要的通知。
  • 因此,在调用notifyObserver方法之前,应该先调用setChanged方法来设置对象的状态已经改变。这样可以确保观察者能够正确地接收到状态改变的通知。
  1. 设计一个控制金鱼缸水质、水温与水位高度的软件系统。基本需求:该程序用于自动控制金鱼缸中的水质、水温与水位高度。系统硬件包含鱼缸、化学传感器、水温传感器与水位传感器。当化学传感器的读数超过某种范围时,鱼缸需要排除部分废水,同时补充新鲜的水;当水温传感器读数低于某温度,或者超过某温度值时,需要开启加热设备或者冷却设备调整水温;当水位读数高于或低于特定高度时,需要开启排水设备,排除部分水或者添加新鲜的水。
    要求使用观察者模式设计该软件系统。具体要求:
    ①画出设计类图。
    ②解释设计的控制程序的风格,说明类图中各软件组件之间的关系及各软件组件所包含的功能。
    在这里插入图片描述

    Observable和Observer为接口类,FishbowlGUI为被观察类(实现Observable接口),ChemistryGUI、TemperatureGUI、LevelGUI为观察者类(实现Observer接口)。
     FishbowlGUI有3个私有变量quality、temperature和level,分别代表鱼缸的水质、水温和水位高度,每个私有变量都有其对应的get和set方法。
     三个观察者类都要实现takeAction(Observable s)方法,每个类实现该方法的方式不同:

    1. 当quality超过特定范围时,化学传感器ChemistryGUI排除鱼缸部分废水,补充新水;
    2. 当temperature低于或高于特定温度时,TemperatureGUl开启加热设备或者冷却设备调整水温;
    3. 当level高于或低于特定高度时,LevelGUI开启排水设备,排除部分水或者添加新鲜的水。
  2. 在这里插入图片描述
    在这里插入图片描述

  • 案例分析
    使用观察者模式设计一个类图来模拟日常快递的领取。在该场景中,快递公司(Express)作为被观察者,实现了被观察者(Observable)接口的方法,能够注册观察者(addObserver)、改变状态信息(setChanged)并通知观察者快递信息(notifyObservers);教师(Teacher)和学生(Student)都是观察者,实现了观察者(Observer)接口的方法,当收到通知信息时采取相应操作(update)。请按要求画出类图及其关键属性和操作。
    在这里插入图片描述

6.4 层次软件体系结构

概念

  • 层次体系结构包括但不限于下述典型的应用领域:
    – 层次通信协议。
    – 数据库系统领域。
    – 操作系统领域。

  • 层次软件体系结构的优点如下。

    • 层次体系结构支持基于随着层次的增加而不断抽象的设计。这使得实现者能将一个复杂问题分解成一系列从初等的与硬件相关的功能到越来越抽象的可以被用户使用的高级功能。
    • 层次体系结构支持更新。每层最多与两个层交互,即该层的上层与下层。因此,在确保接口不变的前提下,可以单独修改或更换某一层的某些组件而不影响其他层。
    • 层次体系结构支持复用。相同的层可以有不同的实现,只要这些不同的实现支持与之相邻的层的相同接口;使用标准的层次接口,每一层都可以由不同的软件开发团队来完成;一个单独的层可以被其他软件复用。
  • 层次软件体系结构的缺点如下。

    • 不是所有的系统都容易被组织成层次结构。
    • 有时即便一个系统可以从逻辑上被组织成一个层次结构,但是出于性能方面的考虑,可能要求层次结构中的逻辑上的较高层次与较低层次之间有比较紧密的耦合,从而迫使软件设计实现者考虑跨层调用,而这时违反层次架构原理的。
    • 正确的层次抽象往往可能有困难。

类图

案例

习题

  • 课堂练习
  1. 指出层次软件体系结构的三个典型应用。
    指出层次软件体系结构的三个典型应用。
    网络体系结构
    操作系统
    应用开发
  • 案例分析

6.5 mvc软件体系结构

概念

  • MVC体系结构将一个互动的应用分为三部分:Model、View、Controller。Model包含核心功能与数据,View为用户显示信息,Controller处理用户输入。
  • 根据责任分离与增加可扩展性的原则,Model应该被设计成独立于特定的输入行为和输出表示的程序。View模块将模型中的数据显示给用户。而因为相同的数据可以有不同形式的显示方法,所以一个Model可以有很多个View。每个View可以拥有与之关联的Controller组件
  • MVC软件体系结构的优点:
    1. 对于同一个模型,可以有不同的视图与控制器,以便提供给用户不同类型的用户图形界面;
    2. 改变- 传播机制保证了模型在改变的同时自动刷新所有的视图,所有的视图都同时实时地反映了模型的现有状态;
    3. MVC体系结构的设计是的改变用户的图形界面变得非常容易,MVC结构非常适合业务逻辑较少改变,而用户图形界面经常要改变的应用;
    4. 由于全部的核心数据与核心功能都包含在模型(Model)中,因此很容易对核心的应用进行测试。

类图

在这里插入图片描述

如图6.99所示,是使用观察者模式的MVC体系结构设计类图。

• Controller从用户图形界面接收用户输入,然后再根据用户输入类型,调用Model的相应业务功能。然后在Model类的withdraw()方法中访问数据库的表进行相应的处理。

• 由于采用了观察者机制,作为观察者的View的自动更新可由Model中的通知方法notifyObservers()执行,然后在View的update()方法中反过来查询到底是什么数据发生了变化,从而更新视图自身。
在这里插入图片描述

案例

在这里插入图片描述
设计类图如图6.104所示。CarAuctionGUI提供用户输入界面,代表用户;CarModel为MVC体系结构中的模型部分(Model);CarAuction和
CarGUIView代表视图部分(View);而Controller为控制器部分。
在这里插入图片描述
在这里插入图片描述

习题

  • 课堂练习
  1. MVC是一种设计模式还是一种软件体系结构?(***)

    MVC(Model-View-Controller)既是一种设计模式,又是一种软件体系结构。
    作为设计模式,MVC旨在解决用户界面和应用程序逻辑之间的分离问题。它将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)
    作为软件体系结构,MVC定义了应用程序的整体结构和组织方式。它提供了一种分层的方式来组织和管理应用程序的各个部分。

  2. 在带有观察者机制的MVC体系结构中,将观察者类中的更新方法
    update(Observable e)的参数类型设为Observable,这样做的好处是什么?

    • 将观察者类中的更新方法的参数类型设为Observable,可以使观察者能够获取到被观察对象的具体信息和状态,从而能够根据实际情况作出相应的处理。这样做的好处包括:
      • 获取被观察对象的状态: 通过将参数类型设为Observable,观察者可以获取到被观察对象的状态信息。这使得观察者能够及时获知被观察对象的更新内容,从而根据具体的状态进行相应的处理。
      • 实现多态性: 通过使用Observable作为参数类型,观察者可以接收任何符合Observable接口的具体实现类的对象。这种多态性的设计使得观察者能够适用于不同类型的被观察对象,而不需要修改观察者类的代码。这增强了代码的灵活性和可扩展性。
        -== 降低耦合度==: 通过使用Observable作为参数类型,观察者与被观察对象之间的耦合度降低。观察者不需要依赖具体的被观察对象类,只需要依赖于通用的Observable接口。这种松耦合的设计使得观察者能够更加独立、可复用和可测试。

    总而言之,将观察者类中的更新方法的参数类型设为Observable使得观察者能够获取被观察对象的具体信息和状态,实现多态性,降低耦合度,从而提高了系统的灵活性、可扩展性和可维护性。

  3. 设计一个火车售票系统,需要有用户图形输入界面,包括车票的出发时间、价格、车次以及当前剩余车票数的显示界面。要求:
    1)车票需要显示以下信息:出发时间、价格、车次、当前剩余车票数;
    2)剩余票数的显示必须立即反映票数的变化;
    3)用户界面易于改变,甚至在运行时改变。
    采用MVC体系结构(使用观察者机制)设计该题,用户输入界面和显示视图独立显示。
    在这里插入图片描述

  • 案例分析
    采用MVC体系结构(使用观察者机制)设计一个汽车销售系统。在该场景中,汽车(CarModel)作为被观察者,实现了被观察者(Observable)接口的方法,能够通知观察者汽车的信息(notifyObservers)、注册观察者(register),并使用列表(observersList )来保存观察者;界面由用户图形输入界面(CarAuctionGUI )、汽车介绍界面(CarGUIView )、剩余数量界面
    (CarBitView)组成,其中CarGUIView和CarBitView都是观察者,实现了观察者(Observer)接口的方法,当收到信息时采取相应操作(update);控制器类(Controller)处理用户输入,使用actionPerformance()方法对用户输入做出响应。要求用户输入界面和显示视图独立显示。请按要求画出类图及其关键属性和操作。
    在这里插入图片描述

基于网络的软件体系结构

  • 特点
    – 面向互联网应用软件,属于分布式计算范畴。
    – 通常要考虑如何更加有效地利用网络上的计算资源。
  • 内容
    – 本章首先介绍早期的客户端-服务器软件体系结构,然后介绍近年来出现的P2P软件体系结构、网格计算软件体系结构、SOA软件体系结构和云计算软件体系结构等。

7.1 客户端-服务器软件体系结构

练习

  1. 用文字叙述层次体系结构与客户端-服务器体系结构的区别。
    在通常的层次体系结构中所涉及的层次指的是逻辑层,而三层客户端-服务器体系结构中所涉及的层是从物理方面考虑的层。即客户端运行在PC上,服务层被部署在应用服务器上,数据库层被部署在数据库服务器上。
  2. 指出两层客户端-服务器体系结构每一层所包含的内容,并且用自己的话描述:
    ① 应用该体系结构设计的程序的工作原理。
    ② 胖客户端,瘦客户端

两层客户端-服务器体系结构是一种常见的软件架构模式,它由两个主要组成部分组成:客户端和服务器。每个组件都有特定的功能和职责。
客户端层:
胖客户端(Fat Client):在胖客户端架构中,客户端负责处理大部分的应用逻辑和数据处理。它具有丰富的用户界面和功能,能够在本地执行复杂的计算任务和数据处理。胖客户端通常需要在本地安装和运行较大的软件包。
瘦客户端(Thin Client):在瘦客户端架构中,客户端主要负责用户界面的显示和用户输入的收集,而应用逻辑和数据处理主要由服务器端完成。瘦客户端相对较轻量,它依赖于服务器提供的应用程序和数据,并通过网络与服务器进行通信和交互。
服务器层:

服务器:服务器是中央处理单元,负责接收来自客户端的请求并提供所需的服务和数据。服务器层包括应用程序和数据的存储、处理和管理。它负责执行复杂的计算任务和数据操作,并将结果返回给客户端。
在这种体系结构下,程序的工作原理如下:

客户端向服务器发送请求,请求可能包括用户输入、数据查询等。
服务器接收到请求后,根据请求的类型和内容,进行相应的数据处理和计算,并生成响应结果。
服务器将响应结果发送回客户端。
客户端接收到服务器的响应结果后,根据需要进行界面的更新和显示,同时可能会涉及进一步的用户操作。
这个过程可能会在客户端和服务器之间进行多次交互,直到完成所需的任务。

  1. 三层客户端-服务器体系结构与两层客户端-服务器体系结构相比,明显的优点有哪些?
    • 在系统管理方面,三层体系结构比较简单,应用可以在服务器端使用系统管理工具进行集中管理。
    • 在数据封装方面,三层结构则较好,通过客户端调用服务或者方法来完成。
    • 在重复应用方面,三层体系结构在复用方面表现良好,可以复用服务与对象。
    • 在遗产应用集成方面,对于三层客户端-服务器体系结构来讲,可以利用封装服务与对象的方式,通过网关、网络连接器、路由器进行遗产应用集成。
    • 在互联网支持方面,三层客户端-服务器体系结构由于在客户端往往只有网络浏览器,所以该方面表现优良。
    • 在多数据库支持方面,在三层结构中,一个商业交易可以使用几个数据库。

7.2 P2P软件体系结构

练习

  1. 请简述P2P体系结构的概念。
    答:P2P分布式网络体系结构由众多的参与者结点,或者称之为对等结点组成,每个结点都可以为其他参与者提供一些服务,各个参与者之间直接交互,而不需要中间网络服务器。每个结点都是资源的提供者,同时又都是资源的消费者。

  2. 第一代到第三代P2P体系结构分别叫什么?
    答:集中目录式P2P体系结构,纯P2P体系结构,非结构化的层次纯P2P体系结构

  3. 请简述集中目录式P2P的优缺点。
    答:集中目录式P2P体系结构的优点为,提高了网络的可管理性,使得对共享资源的查找和更新非常方便。其缺点为网络的稳定性比较差。一旦服务器失效,则该服务器下的对等结点可能全部失效。很明显,中心控制存在单点失效与不可扩展问题,同时中心目录服务器还很可能形成瓶颈问题。

7.3 网格计算软件体系结构

练习

  1. 中国地震预测组织联盟包括几百家地震预报站,每个地震预报站都有高质量的可靠的实验设备,但是每个地震站又无能力对全国地区进行地震预报计算。国家地震局决定综合利用每个站的可靠的计算资源,包括各种传感器数据,以便达到实时性的地震预报。计算包含地址结构变化监测、断裂带监测、地下水位计算等。如果要进行综合实时预报软件的设计,并且要从下列体系结构中进行选择:
    (1)网格计算体系结构;
    (2)P2P软件体系结构。
    那么选择哪种体系结构比较合适?请说明选择这种体系结构的原因。

解:
  网格计算体系结构,网格计算体系结构偏向整合所有资源完成一件事,对性能,服务质量,安全性要求较高,P2P软件体系结构偏向资源的共享,题目中“国家地震局决定综合利用每个站的可靠的计算资源,包括各种传感器数据,以便达到实时性的地震预报”,比较适合网格计算体系结构。

  1. P2P软件体系结构与网格软件体系结构的主要区别是什么?如果要设计一个应用程序,为电影爱好者成立一个虚拟组织,使得电影爱好者可以通过互联网交换影片,那么:
    (1)应从以上的两种体系结构中选择哪种?
    (2)说明选择这个体系结构的原因是什么?

解:
  网格计算强调资源整合、性能、服务质量和安全性等问题,P2P计算关注网络服务质量和支持问题。
(1)P2P软件体系结构
(2)网格计算体系结构偏向整合所有资源完成一件事,对性能,服务质量,安全性要求较高,P2P软件体系结构偏向资源的共享,电影爱好者可以通过互联网交换影片,即为电影资源的共享,比较适合P2P软件体系结构。

7.4 SOA软件体系结构与Web Service

练习

  1. Web Service与SOA体系结构的关系是什么? Web Service有什么好处?
    解:
      SOA可以有不同的实现,而Web Service 就是该体系结构的一种实现。
      Web Services提供了一种能够在异构应用之间进行互操作的标准方案。Web Services可以被松散地组装在一起,以便实现更为复杂的业务操作。即便是原有提供简单服务的程序也可以通过互动而实现服务增值。

  2. 简述SOA体系结构的优势。
    解:
      和正常的 B2B电子商务不同的是,在SOA体系结构中,服务提供者与消费者之间不是静态绑定的,而是可以被自动发现和动态绑定的。SOA支持对企业遗留系统的复用,通过从各种异构的遗留系统中创建新的统一的服务,实现各系统之间的互操作性。

7.5云计算软件体系结构

练习

  1. 云计算都提供哪些类型的服务?
    解:
      基础设施即服务、平台即服务、软件即服务

  2. 有哪些开源云计算软件产品,分别有何作用?
    解:
      Hadoop,Hadoop已被广泛用于一些商业云计算平台中,例如Amazon , Yahoo等公司都采用了Hadoop 。

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值