以下参考自珠海科技学院PPT和广东工业大学。

0 设计模式学习步骤/考试结构

1.模式动机与定义

2.模式结构与分析

3.模式实例与解析

4.模式效果与应用

5.模式扩展

考试题目结构:

  • 概念题:3道*5分 = 15
  • 简答题:4*15 = 60
  • 设计题:1* 25 = 25

1 设计模式概念

1.1 模式

模式是在特定环境中解决问题的一种方案。

模式的经典定义( Alexander ):每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,我们可以无数次地重用那些已有的解决方案,无需再重复相同的工作。

软件模式是模式概念在软件开发领域中的应用,它是对软件开发中某种特定“问题”的“解法”的统一表示,它和 Alexander 所描述的模式定义完全相同,即软件模式等于一定条件下出现的问题以及解决方案。

软件模式并非仅限于设计模式,还包括架构模式分析模式过程模式等。

1.2 模式要素与结构

模式三要素:

Context(模式可适用的前提条件)

Theme或Problem(在特定条件下要解决的目标问题)

Solution(对目标问题求解过程中各种物理关系的记述)。

软件模式的基本结构由4个部分构成:

模式名称

  • 问题描述
  • 前提条件(环境或约束条件)
  • 解法
  • 效果

1.3 定义与要素

**设计模式(Design Pattern)**定义:是一套被反复使用、多数人知晓、经过分类编目的代码设计经验的总结。

使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性

设计模式的要素有名称、问题、目的、解决方案、效果和实例代码等,其中的关键元素包括以下四个方面:

模式名称 (Pattern name)

问题 (Problem)

解决方案 (Solution)

效果(Consequences)

1.4 设计模式的分类

1.4.1 根据使用目的划分

根据模式的使用目的,可分为如下三种:

1.创建型(Creational)模式,用于创建对象,将对象的创建与使用分离。

2.结构型(Structural)模式,用于处理类或对象的组合,将类或对象按某种布局组成更大的结构。

3.行为型(Behavioral)模式,用于描述对类或对象怎样交互和怎样分配职责。

1.4.2 根据范围划分

根据处理类之间关系还是对象之间的关系,可分为类模式对象模式两种。

  1. 类模式处理类之间的关系,通过继承(或实现)建立,在编译时刻就被确定下来,具有静态性。
  2. 对象模式处理对象间的关系,这些关系在运行时刻变化,具有动态性。

1.4.3 设计模式23类

p9t72NV.md.png

另一种表达方式:

p9t7X9O.png

1.5 设计模式优点

软件开发过程中,合理使用设计模式, 使程序呈现高内聚、低耦合的特性,具有如下优点:

1.更好地实现代码重用

2.系统易于扩展(方便增加新的功能,也称可维护)

3.高可靠性 (增加新的功能时,对原有功能没有影响)

4.程序易于理解和交流(因为可以使用UML类图来交流)

2 七大设计原则

设计原则是程序员在编程时应当遵守的原则,也是各种设计模式的基础(或者说是设计的依据)。

面向对象设计原则包括7个,这些原则并不是孤立存在的,它们相互依赖,相互补充。

目的是提高可维护性和可靠性。

p9tbYJf.png

1 开闭原则(OCP)

2 依赖倒置原则(DIP)

3 里氏代换原则(LSP)

4 合成-聚合复用原则(CARP)

5 单一职责原则(SRP)

6 迪米特法则(LoD 或 PLK)

7 接口隔离原则(ISP)

3 类间关系(不考)

在Java语言(以下默认)中,普通类使用关系字class定义,类方法都需要使用一对花括号{…}表示方法体(方法的实现代码)。

抽象类使用abstract class定义,必须包含有使用abstract定义的抽象方法。抽象方法没有使用一对花括号{…}定义的方法体,抽象类也可以包含有方法体的普通方法。

接口使用关键字interface定义,它定义的所有方法默认是抽象方法(尽管通常省略了关键字abstract)

注意:设计模式研究类间关系时,当然会涉及类与类、类与抽象类、类与接口之间的关系,并认为接口是一种特殊的类。

3.1 泛化关系

使用泛化关系(Generalization)也就是继承关系,也称为“is-a”关系。泛化关系用于描述父类与子类之间的关系,父类又称作基类或超类,子类又称作派生类。

在代码实现时,使用面向对象的继承机制来实现泛化关系。在Java语言中,使用extends关键字;在C++ 、C#中,使用冒号“:”来实现。

p9tvMy8.png

3.2 实现关系

实现关系(Realization)是指接口与实现类之间的实现关系,接口实现类需要实现接口中声明的所有抽象方法。

在Java语言中,使用关键字implements来表示实现关系。

示例:类Ship和类Car都实现了接口Vehicle。

p9tvJFs.png

3.3 关联关系

关联关系(Association)表示一类对象与另一类对象之间的有(has a)联系,它是一种表示整体与部分的结构化关系。

在使用C#、C++和Java等编程语言中,关联关系通常是将一个类(接口)类型的对象作为另一个类的属性 。代码中,部分类对象表现为全局变量。

关联关系可分为单向关联和双向关联。

单向关联示例: Customer与Address(顾客与快递地址)

双向关联示例: Employee与Department(员工—部门)

关联关系通常有聚合与组合2种使用方式,它们通过代码加以区分。

3.3.1 聚合

聚合(Aggregation)关系表现在部分类对象可以脱离整体类对象而独立存在。

在代码实现时,部分类对象通过它的构造器或setter方法注入。

p9tvBmF.png

3.3.2 组合

组合(Composition)关系中,部分类对象与整体类对象具有统一的生存期。当整体类对象消亡时,部分类对象也将消亡。或者说,整体类对象控制了部分类对象的生命周期。

在代码实现时,部分类对象在整体类属性声明时或它的构造方法里实例化。

p9tvTkd.png

3.3.3 自关联

在系统中,可能会存在一些类的属性对象类型为该类本身,这种建立自身关联的关系称为自关联。例如:单链表中的结点类。

p9tvO6f.png

3.4 依赖关系

依赖关系(Dependency)是指两个事物之间的使用关系。

一个类只要用(using)到了另一个类,那么它们之间就存在依赖关系。如果没有对方,连编译都通过不了(IDE是自动编译,此时有波浪线提示)。

在代码中,依赖关系通过如下三种方式来体现依赖类:

(1)方法参数类型

(2)方法返回值类型

(3)在方法代码里使用了另外的类。

3.5 UML类图表示

p9NS50U.png

  • 泛化关系用带空心三角形的直线
  • 实现关系用带空心三角形的虚线
  • 关联关系用实线连接有关联的两个类
  • 依赖关系用带箭头的虚线表示

4 创建型模式

创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。

为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。

创建型模式在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。

创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。

创建型设计模式(Creational Design Pattern)描述如何创建对象,将对象的创建和对象的使用分离开,因而符合单一职责原则。由于创建型模式封装了对象的创建细节,客户端无需关心对象的创建细节,这就降低了系统耦合度,因而便于扩展,符合开闭原则。

创建型模式可划分为类创建型模式和对象创建型模式。在GoF23的5种创建型模式中,只有工厂方法模式属于类创建型模式;而抽象工厂、单例、原型和建造者等都属于对象创建型模式。

4.1 工厂模式

p9NpJBT.png

4.1.1 简单工厂模式(不属于23类)

简单工厂模式(Simple Factory Pattern)定义:又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,根据参数的不同返回不同类的实例。

模式动机:

只需要知道水果的名字,就能得到相应的水果。

再考虑一种场景。客户端需要创建和使用不同类型的电视机对象,不同品牌的电视机都源自同一个电视机抽象类。我们希望客户端不需要知道这些具体的品牌电视机名字,只需要知道电视品牌名称并提供一个调用方便的方法,把参数传入方法就可返回一个相应的电视机对象,以实现电视机对象创建与使用的分离。

简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

简单工厂模式的核心是工厂类,在没有工厂类之前,客户端一般会使用new关键字来直接创建产品对象。

在引入工厂类之后,客户端可以通过工厂类来创建产品,在简单工厂模式中,工厂类提供了一个静态工厂方法供客户端使用,根据所传入的参数不同可以创建不同的产品对象。

在客户端代码中,我们通过调用工厂类的静态方法即可得到产品对象。

p9NpTDf.png

p9Npsu6.png

4.1.2 工厂方式模式(类创建型)

模式动机:简单工厂模式如果需要增加新的产品类型,那么需要修改工厂类的代码,这就使得整个设计在一定程度上违反了“开放封闭原则”。我们定义一个抽象工厂类,并在具体工厂类里重写这个抽象按钮工厂类中定义的抽象方法。抽象化的结果使这种结构可以在不修改已有具体工厂类的情况下引进新的产品。

工厂方法模式(Factory Method Pattern)定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。

在工厂方法模式中,父类负责定义创建对象的公共接口,而子类则负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。

工厂方法模式属于类创建型模式。

p9NpR4H.png

p9NCkef.png

p9NCETS.png

4.1.3 抽象工厂模式

**模式动机:**在工厂方法模式中,每个具体工厂对应一种具体产品。有时,我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。

抽象工厂模式(Abstract Factory Pattern定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

抽象工厂模式属于对象创建型模式

抽象工厂模式与访问者模式有一定的相似性。

p9NPVnx.png

如果增加新的品牌,即增加一个抽象工厂的子类,则只需要相应地增加该工厂生产的产品类。因此,抽象工厂模式符合开闭原则(如同工厂方法模式)。

如果增加新的产品类型,则需要添加相应的抽象产品及其子类。同时,还需要在抽象工厂里添加新的抽象方法,在具体工厂重写新增的抽象方法。因此,抽象工厂模式属于对象创建型模式,且不支持添加新的产品类型。

由于工厂方法模式只有一个产品类型,因此,工厂方法模式是抽象工厂模式的退化。

p9NPnAO.png

4.2 单例模式

模式动机

对于系统中的某些类来说,有且只能有一个实例。

(1)一个系统只能有一个窗口管理器。

(2)系统中可以有许多打印机,但是只能有一个打印机正在工作。

模式定义:

单例模式(Singleton Pattern)定义:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。

单例模式属于对象创建型模式。

单例负责类的实例的创建,且是唯一的。因此,在单例类的外部无法使用new创建。否则,该类的实例对象就不是单例。

模式结构:

p9NPa4g.png

模式示例

p9NPyD0.png

4.3 原型模式

模式动机

有些对象的创建过程较为复杂,而且有时候需要频繁创建,原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。这就是原型模式的动机。

模式应用场景:

原型模式用以实现类型相同、内存地址不同的对象复制。

原型模式通过复制一个已有实例简化对象的创建过程。

很多软件提供的复制、粘贴功能都是原型模式的应用。

模式定义

原型模式(Prototype Pattern)定义:采用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式允许一个对象再创建另外一个可定制的对象,无需知道任何创建的细节。

模式结构:

p9NiFIS.png

模式实现

原型模式的实现表现在重写的抽象方法,可以划分为通用和专用两种实现方式。

原型模式的通用实现方式是在抽象方法里使用new运算符创建原型对象,将相关参数传入新建的原型对象后再返回。

通过克隆方法创建的对象是全新的对象,在内存中拥有新的地址。

原型模式的专用实现方式使用Java语言中顶级对象Object定义的克隆方法clone()和不含任何抽象方法的克隆接口java.lang. Cloneable。

4. 4 建造者模式

建造者模式考虑复杂产品的构建,实现产品构建与它的表示分离。

使得同样的构建过程可以创建不同的表示。

允许用户通过指定复杂对象的类型和内容就可以构建完成,而不需要知道内部的具体构建细节。

建造者模式与工厂方法模式的出发点不同、类图部分相同(泛化-依赖) 。

装饰模式属于结构型,用于动态地给已有对象添加额外的功能。

建造者模式与命令模式有一定的相似性(存在三种关系:聚合-泛化-依赖)。

模式动机

模式动机:在某些情况下,一个对象会有一些重要的属性,在它们没有恰当的值之前,对象不能作为一个完整的产品使用。比如,一个电子邮件有发件人地址、收件人地址、主题、内容、附录等部分,至少收件人地址未被赋值之前,这个电子邮件不能发出。

模式定义

建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式是一步一步创建一个复杂的对象,它允许用户通过指定复杂对象的类型和内容就可以构建完成,而不需要知道内部的具体构建细节。

模式结构

p9NFeYD.png

产品指挥者****Director的引入,使得客户端不需要了解产品的构建细节。

通过Director控制复杂产品的建造过程。相同的建造过程,通过使用不同的建造者,可以得到不同的产品表示。

实际应用本模式时,需要在ConcreteBuilder里定义与Product相同的字段,以便返回完整的产品。

5 结构型模式

在GoF提出的23种设计模式中,依次包含了外观、适配器、组合、代理、桥接、装饰和享元七种。

结构型设计模式描述如何将类或对象结合在一起形成更大的结构,并可划分为类结构型模式和对象结构型模式。只有类适配器模式是类结构型,其他结构型模式都是对象结构型模式。

类结构型模式关心类的组合,由多个类可以组合成更大的系统,一般只存在继承关系和实现关系。

对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。

根据合成-聚合复用原则,在系统中尽量使用关联关系来替代继承关系。

5.1 外观模式

模式动机

只需要与外观对象打交道,不需要与子系统内部的很多对象打交道。

一台计算机由CPU、内存和硬盘等子系统组成,机箱聚合了其它子系统,只需要按机箱上的电源就可以让整个系统正常工作。

外观类屏蔽了各个子系统的内部细节,客户端Client只需要与外观类Façade对象打交道。

外观模式是迪米特法则的典型应用。其中,外观类对象充当Client各个与SubSystem的第三者。参见中介者模式。

模式定义

外观模式(Facade Pattern)又称门面模式,定义:外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

在外观模式中,一个子系统的外部与其内部的通信通过一个统一的外观类进行,外观类将客户端与子系统的内部复杂性隔离开,使得客户端只需要与外观类角色打交道,而不需要与子系统内部的许多对象打交道。

模式结构与角色

p9NFr7V.png

示例

p9NFOcd.png

5.2 适配器模式

模式动机

将一个接口转换成客户希望的另一个接口,使接口不兼容的那些对象可以一起工作。例如,笔记本电脑的工作电压是DC 20V,我们的生活用电是220V。正常使用笔记本电脑,必须有一个电源适配器。

在软件开发中,适配器模式将现有的接口转换为客户类期望的接口,用以保证对现有类的重用,或扩展现有类的功能。

模式定义

适配器模式(Adapter Pattern)定义:将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。

在使用上,适配器模式可以划分为类适配器和对象适配器两种。类适配器使用类继承方式,对象适配器不使用类继承方式,而是使用关联(通常是组合或聚合)方式。

角色1:适配者Adaptee,是已存在且具有特定功能但不符合目标接口的类,表示需要被适配的对象。

角色2:目标Target,表示被适配后的对象。

角色3:适配器Adapter,表示适配器对象。

类适配器

适配器模式将现有的接口转换为客户类期望的接口,用以保证对现有类的重用,或扩展现有类的功能。

适配器模式有2种使用方式:类适配器和对象适配器。

类适配器的特点是:Adapter继承Adaptee。与桥接模式辨析。

适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。装饰模式的别名也是包装器

p9NFxBt.png

对象适配器

p9Nki9g.png

对象适配器与类适配器角色相同,而类(接口)间关系不同。

​ Adapter是Target的子类, Adapter聚合Adaptee。

对象适配器模式中的Target也可以被设计为接口。

5.3 组合模式

模式动机

树形结构在软件开发中随处可见,如操作系统的文件目录结构、应用软件中的菜单结构和公司组织机构等。

模式定义

组合模式(Composite Pattern)定义:组合多个对象形成树形结构以表示具有部分—整体关系的层次结构。组合模式让客户端可以统一地对待单个对象和组合对象。

组合模式又称部分—整体(Part—Whole)模式,它将对象组织到树形结构中,用以描述整体与部分的关系。

组合模式存在子类聚合父类,参见装饰模式和解释器模式。

组合模式是对象结构型模式。

组合模式根据抽象构件的定义形式,可划分为透明组合模式和安全组合模式。

透明组合模式

透明组合模式一致地看待叶子结点和容器结点。

安全组合模式

在安全组合模式里,叶子结点和容器结点拥有的方法有差异

在测试程序里,使用Composite创建容器结点。

安全组合模式中“安全”的含义是叶子构件不具有容器构件特有的add(component)等方法。

根据透明组合模式的代码,改写成安全组合模式的代码,是重点。

5.4 代理模式

模式动机

模式动机:在网页上查看一张图片,由于网速等原因图片不能立即显示,我们可以在图片传输过程中先把一些简单的用于描述图片的文字传输到客户端,此时这些文字就成为了图片的代理。

应用场景

一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用,去掉客户不能看到的内容和服务或者增添客户需要的额外服务。

模式定义

n代理模式(Proxy Pattern)定义: 给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate。

模式结构

**静态代理:**代理类所实现的接口和代理方法都已确定,代理类在编译后都会生成一个.class文件,我们称这种代理为静态代理(Static Proxy)。

静态代理使用的局限性主要表现在如下方面:

(1)如果需要为不同的真实主题类提供代理或者代理一个真实主题类中的不同方法,都需要增加新的代理类,即静态代理可能产生类爆炸。

(2)如果需要代理的方法很多且使用增强方法,则导致代理类的不同方法里存在大量相同的代码,即静态代理的重用性不强。

动态代理可以让系统在运行时根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的目标类及不同的方法。

动态代理在事务管理和AOP(Aspect Oriented Programming,面向切面编程)等领域都有很广泛的应用。

远程代理(Remote Proxy)是一种常见的代理模式,它隐藏了网络的通信细节,使得客户端可以访问在远程主机里的对象。通俗地说,远程代理是远程对象的本地代表,更一般地说,远程代理是在不同地址空间运行的远程对象。

5.5 桥接模式

模式动机

以使用手机打电话功能为例:从品牌外观样式实现两个维度考虑。

模式定义

桥接模式(Bridge Pattern)定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。

多层继承可能引起类爆炸,桥接模式是比多层继承更好的解决方案。

模式结构

5.6 装饰

**模式动机/定义:**不改变原有对象的前提下,给某个对象而不是整个类添加一些功能。

应用背景:例如,一个图形用户界面工具箱允许对任意一个用户界面组件添加一些特性,如边框、或是一些行为(如窗口滚动)。

装饰模式在运行时动态扩展一个对象的功能,比使用继承(编译时期就已经确定关系)更加灵活,在Java IO中有广泛的应用。

装饰模式与组合模式和解释器模式类似,也存在子类(装饰器)聚合抽象的父类。

与适配器模式一样,装饰模式的别名也是包装器

n抽象类Decorator作为抽象类Component的子类,可以不重写父类的抽象方法,而是延迟到具体类!

n抽象装饰类Decorator 是装饰模式的核心类,维护一个抽象构件(父类Component )的成员对象,并使用构造器注入。

n装饰者和被装饰者可以独立变化。用户可以根据需要增加新的装饰类,在使用时再对其进行组合,原有代码无须改变,符合开闭原则。

n装饰者模式可以在运行时动态扩展一个对象的功能,比继承(编译时期就已经确定关系)更加灵活。

5.7 享元

模式动机:在进行共享单车平台软件设计时,按照单车的种类创建对象,需要使用享元模式,以减少创建对象的数量和内存消耗。

享元模式运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很近,状态变化很小,对象使用次数增多。

6. 行为型模式

在GoF 23种设计模式中,依次包含了策略、模板方法、备忘录、观察者、迭代器、命令、状态、职责链、中介者、访问者和解释器等11种行为型设计模式(Behavioral Design Pattern) ,它们的使用频率由高向低,学习难度由低向高。

行为型设计模式关注系统中对象的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责。行为型模式不仅关注类与对象本身,还重点关注它们之间的相互作用和职责划分。

行为型模式可划分为类行为型模式对象行为型模式两种。类行为型模式使用继承关系在若干类之间分配行为,主要通过多态等方式来分配父类与子类的职责,模板方法和解释器模式属于类行为型模式。对象行为型模式使用对象之间的关联关系来分配行为。由于要坚持合成-聚合复用原则,因此,大部分行为型模式属于对象行为型设计模式。

行为型设计模式主要解决的就是“类或对象之间的交互”问题。

策略模式用于算法的自由切换和扩展。

模板方法模式是基于继承的代码复用技术,可以引入钩子方法使得子类可以控制父类的行为。

命令模式和备忘录模式都可以实现多次的撤销和恢复功能。

观察者模式是一种使用频率非常高的设计模式,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。MVC架构是观察者模式的典型应用。

命令模式和职责链模式都实现了请求发送者与处理者的解耦。例如:命令模式中的Invoker调用Command, Command调用Receiver。

中介者模式是迪米特法则的典型应用,只是做一系列对象之间的消息转发,本身不产生消息。

复习

继承、合成、聚合的差别

继承、合成和聚合是三种常见的代码复用方式,它们之间的区别如下:

  1. 继承(Inheritance):子类继承父类的属性和方法,子类可以重载父类的方法或者新增自己的方法。继承使得子类与父类之间形成一种“is-a”(是一个)的关系,即子类是父类的一种具体化。
  2. 合成(Composition):通过将多个对象实例组合在一起,来实现新的功能。例如,一个“汽车”对象由“发动机”、“轮胎”、“座位”等组成。合成使得对象之间形成了一种“has-a”(有一个)的关系。
  3. 聚合(Aggregation):聚合与合成相似,但聚合的组成部分是可以单独存在的,不一定依赖于组合对象。例如一个“公司”对象由多个“员工”对象组成,但员工可以独立于公司存在。聚合也是“has-a”关系。

继承具有类似“父子”间的层级关系,而合成和聚合则更多地强调“组合”关系。在具体的设计和实现中,应选择合适的代码复用方式。