UML8设计模式(GRASP模式、GOF模式)

软件系统建模与设计 第八部分 设计模式初步

8.1 设计模式的定义与作用

基本概念\r\n定义\r\n对软件设计问题的可重用的解决方案。\r\n\r\n作用\r\n重用设计比重用代码更有意义,可充分利用已有的软件开发经验\r\n为设计提供共同的词汇,方便交流和书写开发文档\r\n降低错误可能性\r\n节省时间

面向对象设计模式分类\r\n\r\nGRASP ( General Responsibility Assignment Software Patterns,通用责任分配软件模式)\r\n\r\nGoF (Gang of Four,“四人帮”设计模式)

8.2 面向对象设计模式分类

GRASP模式\r\n责任是类间的一种合约或义务,也可以理解成一个业务功能,包括行为、数据、对象的创建等\r\n\r\n知道责任——表示知道什么\r\n\r\n行为责任——表示做什么

8.3.1 Grasp模式基本概念

GRASP模式\r\n责任=知道责任+行为责任\r\n了解私有封装数据\r\n了解关联的对象\r\n了解能够派生或计算的事物\r\n完成对象初始化\r\n执行一些控制行为

8.3.1 Grasp模式基本概念(续1)

知道责任

行为责任

责任不是类的方法,类的方法用于实现行为责任。责任更可以理解成是系统应提供的一个业务功能\r\n\r\n责任的分配可使用顺序图或协作图来表达\r\n\r\n面向对象设计过程就是将责任分配给对象的过程

8.3.1 Grasp模式基本概念(续2)

8.3.2 Grasp模式举例

行为责任表示交费的行为,需要创建一个付款记录的对象Payment。\r\n\r\n知道责任必须知道付款记录类Payment,知道如何记录及计算Payment类中的数据。

在一个销售业务中,存在一个交费行为,可将它识别为一个责任。

发现具有交费行为的对象Sale\r\n发现了该类还关联另一个类对象Payment\r\n定义了两个部署在不同类上的方法makePayment和create

8.3.2 Grasp模式举例(续1)

GRASP模式的分类\r\nInformation Expert(信息专家)\r\nCreator(创造者)\r\nLow Coupling(低耦合)\r\nHigh Cohesion (高内聚)\r\nController (控制器)\r\nPolymorphism (多态)\r\nPure Fabrication (纯虚构)\r\nIndirection (间接)\r\nProtected Variations (受保护变化)

8.3.3 Grasp模式详解

8.3.3.1 信息专家

Information Expert(信息专家)\r\n谁能够在某方面具有完整的信息,足以实现某个责任,就将责任分配给这个类,这个类即所谓的信息专家。\r\n\r\n总结为“谁知道谁负责”

8.3.3.1 Grasp模式-信息专家举例

在购物车系统中,要让每个商品(Item)在购物车(ShoppingCar)中只出现一次,如果放相同的商品到车上就只更新该商品的数量而不增加商品项

Creator(创造者)\r\nA是B的聚合或容器\r\nA有初始化B所需的信息\r\nA需要记录B的实例

8.3.3.2 创造者

具有以上特性,可让A具有创建/创造B类对象的责任

8.3.3.2 创造者举例

Low Coupling(低耦合)\r\n减少类间的耦合(关联/聚集/依赖),使一个类的修改对其它类的影响范围有所降低,从而系统变得更容易维护\r\n使得系统变得更容易理解

8.3.3.3 低耦合

总结为“不要和陌生人说话”

8.3.3.3 Grasp模式-低耦合举例

High Cohesion (高内聚)\r\n提高类的通用性,并控制类的复杂程度,努力分解类使得类具有独立的责任。\r\n低耦合与高内聚貌似互相矛盾的两个目标。\r\n非常低的内聚:一个类单独处理很多不同模块的事务。比如它既处理对数据库的存取,又处理用户接口图形处理。\r\n比较低的内聚:一个类单独处理一个模块内的所有事务。\r\n高内聚:类只处理与模块相关的功能,一个类具有一个相对独立的责任,且与其它类合作共同完成任务。

8.3.3.4 高内聚

8.3.3.4 高内聚举例

准备分配新的责任时,该类中的其它功能是否与这个责任有一定的关联,如果不是,最好把责任分配给别的类,甚至新建一个类处理这个责任。

问题:有一个订单类,现要增加一个用于支持存取Excel表中以支持不同数据来源的责任,这个责任应该加入订单类吗?

8.3.3.4 高内聚举例(续1)

订单的详情和数据的存取不是同一个概念,它们甚至不相近,因此新建一个OrderDAO类来负责数据存取这个职责

低内聚的缺点\r\n很难被理解和维护,可能包含近百个方法或者属性、成千上万个方法,难实现类的重用,系统脆弱不断需要修改\r\n高内聚的优点\r\n高内聚可表现关联责任的一个抽象,易于实现类的重用\r\n高内聚使维护工作变得简单\r\n高内聚使得系统模块化工作,方便团队工作

8.3.3.4 高内聚举例(续2)

8.3.3.4 高内聚举例(续3)

Controller (控制器)\r\n能全面代表系统或子系统的类,比如系统事件的接收和处理通常由一个高级类来代替,称为控制器类\r\n不要试图只定义一个控制器类,那样会违反高内聚的原则,一个子系统会有多个控制器类,分别处理不同的事务\r\n控制器不是用户界面类,但通常与界面类关联\r\n联系:MVC模式三层架构

8.3.3.5 控制器

8.3.3.5 控制器(MVC)

8.3.3.5 控制器举例

在银行系统中,会有一个控制器类用于处理所有的银行事务。

8.3.3.6 多态

Polymorphism(多态)\r\n在OOP看来,提供了静态多态和动态多态,前者包括函数重载和模板两种形式,都是在编译期根据参数类型检查来确定调用哪个函数或使用哪个具体参数类型;后者运行时即时编译根据内存和虚函数表查找确定调用哪个函数\r\n多态,尤其是动态多态性使得系统具有不变应万变的特性

8.3.3.6 多态举例

8.3.3.6 多态举例(续)

Pure Fabrication(纯虚构)\r\n和多态性是同一概念,虚构顶层基类针对抽象编程。\r\n纯虚构就是要虚构一个基类,将对象尽量组织成继承树的形式,客户端代码只引用了基类的形式,虽然高内聚类较多但并没有造成高耦合。

8.3.3.7 纯虚构

8.3.3.7 纯虚构举例

设计一个绘图类,要求能在不同的系统如Linux以及Windows下绘画,如何满足?设计一个高层次的抽象类,用于分配这个职责。

Entity类是高层次的抽象类,定义了一个抽象方法draw

8.3.3.8 间接

Indirection(间接)\r\n增加一个中介类,用于避免两个类直接耦合。\r\n在实体世界向关系世界的转化中,对多对多的关系需要增加一个实体转换为两个一对多关系。

8.3.3.8 间接举例

将员工类和岗位类间的多对多关系转换为两个1对多的关系

8.3.3.9 受保护变化

Protected Variations(受保护变化)\r\n估计出需求中容易变化的点,为其设计稳定的接口,也就是开闭原则:对于修改是关闭的,对于扩展是开放的。\r\n通过扩展已有部件,可以提供新的行为以满足新需求,就使变化中的软件系统具有一定的适应性和灵活性。\r\n已有的软件模块,最重要的抽象层模块不能再修改,就使变化中的软件系统具有一定的稳定性和延续性。\r\n仍然是继承多态的一种理解。

GoF设计模式\r\nCreational(创建型)\r\nStructural(结构型)\r\nBehavioral(行为型)

8.4 GoF设计模式

与对象创建有关

处理类/对象间如何交互及分配职责

处理类/对象间的组合

8.4.1 创建型-工厂模式

Simple Factory Pattern(简单工厂模式)

Abstract Factory Pattern(抽象工厂模式)

Factory Method Pattern(工厂方法模式)

8.4.1.1 简单工厂模式

涉及三种类\r\n工厂类(Creator)\r\n根据传入的参数判断应创建哪个具体产品类对象\r\n抽象产品类(Product)\r\n所有具体产品类的父类,实际是一个接口。\r\n具体产品类(Concrete Product)\r\n具体产品对象的类

8.4.1.1 简单工厂模式(续1)

Product:抽象产品类\r\nConcreteProduct:具体产品类\r\nCreator:简单工厂类

8.4.1.1 简单工厂模式(续2)

工厂类负责创建具体的产品类对象,它根据传入的参数进行条件判断

8.4.1.1 简单工厂模式(续3)

8.4.1.1 简单工厂模式(小结)

简单工厂模式中,工厂类处于核心,它根据传入参数决定何时实例化哪个具体产品类。\r\n客户只需要知道传入创建的参数,创建产品的过程与客户端独立。\r\n引入新产品时无须修改客户端类,但要修改工厂类加入判断处理逻辑和创建对应具体产品类。\r\n工厂类当无法确定要实例化哪个类时就不能使用该模式,工厂方法模式可以解决这个问题。

8.4.1.1 何时应用简单工厂模式

下面的情景适合应用简单工厂模式:\r\n客户只知道传入工厂类的参数,不关心如何创建对象。\r\n工厂类负责创建的对象比较少,以后增加新产品的几率不大。

8.4.1.2 工厂方法模式

增加一个工厂类的抽象基类,这样针对抽象编程,由具体工厂类创建具体产品类对象。GoF工厂模式以此为代表,又称为虚拟构造器(Virtual Constructor)或多态工厂(Polymorphic Factory)模式。

8.4.1.2 工厂方法模式(续1)

实际系统中,抽象工厂类出现在Client类的一个方法形参中,实参即具体工厂类由外部模块调用传入,因此对客户端模块完全隐藏了具体的工厂类和具体的产品类。

8.4.1.2 工厂方法模式(小结)

工厂方法模式的核心是一个抽象工厂类,具体工厂类负责创建产品,Client端只关心抽象产品类和抽象工厂类,不需要知道具体产品是如何被具体工厂创建的。\r\n当增加新产品类时,客户端不须修改,工厂类也无须改变,只需要增加新的具体产品类和新的具体工厂类。

8.4.1.2 何时应用工厂方法模式

下面的情景适合应用工厂方法模式:\r\n类不知道自己该创建何种对象,就构造一个抽象工厂类做为基类,在客户端创建产品的方法中传入具体工厂实例针对抽象编程。

8.4.1.3 抽象工厂模式

提供一个创建一系列相关或相互依赖对象的接口(抽象工厂类),而无须指定它们具体的类,又称为Kit模式。\r\n不同于工厂模式针对一个产品等级结构,即所有具体产品类具有相同的基类;抽象工厂模式中有多个不同基类的继承树产品,不同根下的产品组成一个产品系列(产品族)。\r\n客户端使用抽象工厂类获得一个产品族。

8.4.1.3 抽象工厂模式(举例)

例如兵工厂作为抽象工厂类,需要创建飞机、坦克、军舰等战斗机器,还要创建使用它们的飞行员、坦克手、舰员等士兵,这是两类不同但有依赖关系的产品对象,同时需要定义三个具体工厂类来表达这种需要同时创建这两种具体产品对象。\r\n又比如,动物世界类作为抽象工厂需要创建不同地域的动物生存环境。动物有食草动物和食肉动物两种,非洲动物世界有食肉的狮子和食草的角马;美洲动物世界有食肉的美洲狼和食草的野牛。

8.4.1.3 兵工厂工厂模式类图

8.4.1.3 动物世界工厂模式类图

8.4.1.3 抽象工厂模式(小结)

定义一个抽象的工厂类便于替换具体的工厂。\r\n客户端只看到了抽象工厂基类,其它类都不可见,且保证了客户端始终只使用了同一个产品族的具有依赖关系的不同对象。\r\n抽象工厂模式首先是工厂模式,当具有产品族的特性时就称为了抽象工厂模式。

8.4.1.3 何时应用抽象工厂模式

系统需要多个关联的对象,因此创建时需要建立对应的工厂类来建立产品族。

创建型-Singleton单例模式

懒汉方式\r\n当客户端需要获取对象时进行首次实例化\r\n多线程的问题\r\n贪心方式\r\n类加载时即进行对象的首次实例化

如果对一个类,希望只能在系统中存在一个它的对象,称之为单例(单个实例),当第一次使用某方法时创建对象实例,以后调用该方法获得已实例的对象指针或引用。\r\n在GOF设计模式中,称之为单例模式。

首先需要定义私有的构造函数,迫使用户不能随意使用new Singleton()来创建对象。\r\n其次需要定义一个唯一的成员来保存单个实例的指针或引用。\r\nclass Singleton\r\n{\r\n  private static Singleton _instance;\r\n  private Singleton()\r\n  {\r\n    //...\r\n  }\r\n  public static Singleton getInstance()\r\n  {\r\n    if (_instance==null) ----------------------------------------------1\r\n      _instance=new Singleton();-------------------------------------2\r\n    return _instance;\r\n  }\r\n}

前面简单的实现,但存在多线程的安全问题。当有多个进程或线程调用该方法getInstance来获得实例对象时,比如两个线程或进程P和Q,如果是P1Q1Q2P2的执行顺序,就会创建两个Singleton对象。

Double Check(双重检查)

使用DOUBLE CHECK方法,JVM(JAVA VIRTUAL MACHINE,JAVA虚拟机)不支持,因此实际上3处的语句在编译优化时会被编译器去掉,没有效果。

结构型


适配器模式\r\n代理模式\r\n组合模式\r\n装饰模式

结构型-Adapter适配器模式

目标抽象类:用户需要的特定应用的接口。\r\nAdaptee:已有的应用的接口,适配器母接口\r\nAdapter:控制转发类,适配器公接口。

类图-Adapter适配器模式

不能或不想修改一个已有的接口Adaptee(称之为适配器母接口),改变一下调用方式。\r\n将一个接口转换成客户想要的另一个接口。\r\n适配器模式使接口不兼容的对象可以一起工作。\r\n很像代理模式。

小结

结构型-Proxy代理模式

为其它对象提供一个代理或地方以控制对这个对象的访问。当客户向Proxy对象第一次提出请求时,Proxy实例化真实的对象,并且将请求传给它,以后所有的客户请求都经由Proxy对象传给封装了的真实对象。

依据功能分为几种类型

Virtual Proxy(虚拟代理)\r\n实例化花费太大时,首次请求时生成,以后使用缓存机制加快对真实对象的使用。\r\nRemote Proxy(远程代理)\r\n控制一个远程的对象,隐藏远程连接的细节\r\nProtection Proxy(保护代理)\r\n代理检查调用真实对象的权限角色\r\nSmart Reference(聪明引用)\r\n当调用真实对象时,附加一些另外的处理。

与Object接口一致,使得代理可以用来替换实体RealObject。\r\n一个代理对象对应一个具体实体对象,代理对象负责控制对具体实体的访问并负责创建和删除它。\r\n其它功能取决于代理的类型。

文档编辑器图片加载举例

当需要设计一个文档编辑器,文档中需要装载和显示很多图片。如何才能使图片的处理不会对装载和编辑文档的速度有很大的影响?

在图片出现在可见部分时才从数据库装载真实的图片。用图片代理对象负责图片加载这个额外行为,它是文档对象的代理,当图片当前不可见时就不加载。

小结

面向方面编程AOP实际上就是聪明引用这种代理。

Composite组合模式

组合多个对象形成树型结构以表示整体-部门的结构层次。\r\n组合模式对叶子对象和组合对象的使用提供了一致性接口。

涉及到三种类/对象

Component抽象类\r\n抽象的树节点类,定义公共的操作树的方法(add,remove,getchild)和一个操作树节点的抽象方法(operation)\r\nLeaf叶子类\r\n具体的树的叶子节点类\r\nComposite组合类\r\n含有子节点的类

类图

资源管理器中。最后一级子目录是叶子对象,其它节点是组合对象。

资源管理器类图

举例

文档格式化程序:\r\n将字符(Character)格式化为行(Line),行组成栏(Column),栏组成页(Page)。一篇文档(Document)除了包含很多页,还可能包括图片(Image),栏和页之间还可以有框(Frame),框中也可以容纳栏。栏、框、行都可以包含图片。

原始的设计

点评

优点\r\n真实反映了现实对象间的关系,是通过CRC规则发现了这些对象名词得到的初步类图。\r\n缺点\r\n关联复杂。\r\n公共性的抽象提取不够。\r\n某个对象虽然从语义上是包含另一个对象的,但也是可以动态变化包含的。

组合模式改进方案

小结

对象的结构是动态的并且复杂程度不同,但客户需要一致的访问处理。\r\n表达整体/部分关系。

Decorator装饰模式

动态地给一个对象增加其它职责。\r\n比生成子类实现更为灵活。

包含四种类

Component\r\n定义实体对象的接口,可以给这些对象动态的增加职责。\r\nConcretComponent\r\nDecorator\r\nConcreteDecorator

行为型

观察者模式\r\n迭代器模式

行为型-Observer观察者模式

多个观察者对象\r\n一个目标对象\r\n在目标对象上注册观察者对象数组\r\n对不同的观察者对象类定义同样的接口基类,实现update(Object)方法完成事件响应。

行为型-Observer观察者模式

如果一个对象的某种行为会导致一批对象的行为,就将前者称之为目标对象Object,后者称之为观察者对象Observer。目标对象的行为发生,称之为某种事件发生,对应观察者对象需要做出行为来响应。这种紧密依赖的一对多关系很适合使用观察者模式来模拟事件机制。

行为型-Observer观察者模式

定义具有公共的update(p: Object)接口方法的基类作为所有观察者对象的基类,而具体的观察者子类对象实现的update方法就是事件响应方法;在目标对象Object上定义attach(x: Observer)和detach(y: Observer)注册和解注册方法;并在目标对象上定义notify()方法通知其上注册的一批观察者对象,而notify方法实际上是遍历目标对象上的观察者数组,调用每个观察者对象的update方法做出事件响应动作。\r\nObserver观察者模式给出了事件/响应模型的实现。

猫和老鼠的关系举例

老鼠外出需要时刻注意猫,老鼠是观察者\r\n猫发生一个事件,大叫一声“喵”时,所有老鼠纷纷发出事件响应-逃跑。猫是目标对象。\r\n事件响应动作定义为观察者对象上的方法:update(ob:Object),其中参数表示事件源\r\n目标对象上方法attach(obs:Observer),表示该目标上有多少个观察者在观察,称之为在目标对象上注册观察者对象数组。

类图

问题

如何通知老鼠?cry()怎么写?

思考题

股票系统中,每当个股变化时,就会通知给关注该股票的投资者。\r\n谁是观察者?\r\n谁是目标对象?

小结

一事件/多响应机制\r\n事件监听器

迭代器模式

提供一种方法访问一组聚合对象,但不用暴露对象的内部表示\r\n就是java里面的Iterator

涉及到4种类

抽象迭代器接口\r\n定义一个访问和遍历元素的接口\r\n具体迭代器\r\n实现迭代器接口\r\n抽象聚合类\r\n定义创建迭代器对象的接口\r\n具体聚合类\r\n实现创建迭代器对象方法,返回具体迭代器实例