设计模式概述

设计模式的背景

  • 设计面向对象软件比较困难,而设计可以复用的面向对象软件更加困难
  • 不是解决任何问题都需要从头做起,最好能复用以往的设计方案经验
  • 面向对象软件设计经验需要有一定的模式记录下来,以提供给其他设计者使用,这就产生了设计模式。

什么是设计模式

设计模式 (Design pattern)是一套被反复使用的、经过分类编目的面向对象程序设计经验总结。它是面向对象程序设计中典型问题的解决方案。

GoF模式是四位著名面向对象设计专家 (Elich Gamma、Richard Helm、Ralph Johnson、 John Vlissides) 所提出的23种面向对象程序设计模式。这四个人常被称为Gang of Four,简称GoF。

设计模式作用

  • 帮助设计者更快、更好完成面向对象程序设计。
  • 提高实现代码编写的规范性,标准化开发软件构件。
  • 支持编写可扩展、易维护、好管理的程序代码。
  • 让代码更容易被他人理解,确保代码可靠性、重用性。

设计模式类型

序号 模式类型 说明
1 创造模式 与对象创建有关,提供一种创建对象而隐藏创建逻辑,给出灵活创建对象的解决方案。典型模式有5种。
2 结构模式 处理类或对象的组合,给出利用继承、接口组合对象以获得新功能的解决方案。典型模式有7种。
3 行为模式 用于描述对象之间协作完成特定功能及其职责分配,给出对象之间通信的解决方案。典型模式有11种。

创建型模式

创建型模式的关注点是“怎样创建对象?”它的主要特点是“将对象的创建与使用分离”,从而降低对象之间的耦合度。

一、创建型模式类型

  • 单例模式 (Singleton Pattern)
  • 工厂模式 (Factory Pattern)
  • 抽象工厂模式 (Abstract Factory Pattern)
  • 建造者模式 (Builder Pattern)
  • 原型模式 (Prototype Pattern)

单例模式 (Singleton Pattern)

单例模式是指一个类只能有一个实例,且该类能自行创建这个实例的一种程序设计模式。

模式动机

虽然软件系统可以创建多个进程任务并发运行,但系统在同一时段只允许一个进程任务运行处理。如下一些计算机软件程序需要采用单一进程处理。

  • 打印机程序
  • Windows 的回收站
  • 操作系统中的文件系统
  • 多线程中的线程池、数据库的连接池
  • 应用程序的对话框
  • Web应用的配置对象

模式解决问题

如何让系统只允许唯一的实例进行服务资源访问,从而解决多实例模式带来的系统开销问题。

解决问题思想:

  • 单例模式类只允许创建一个实例对象;
  • 该实例对象必须在单例模式类初始化时自行创建;
  • 单例模式类对外仅提供一个访问该单例的全局访问点。

模式设计方案

  • 采用一个类仅能创建一个实例对象。
  • 该类需自行创建这个实例对象。
  • 该类提供全局公开方法让外部来获取这个实例对象。

image-20231224115905082

模式实现方案

饿汉式单例类:在单例类被初始化加载时,就实例化一个对象交给自己的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 饿汉式单例类
public class Singleton {

private static Singleton singletonVar = new Singleton();//在定义变量时就创建对象

private Singleton() {
} //私有的构造方法

public static Singleton getInstance() { //静态公有方法返回创建的对象
return singletonVar;
}

}

懒汉式单例类:在访问实例对象时才实例化对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
private static Singleton singletonVar; //私有的静态对象变量

private Singleton() {
} //私有的构造方法

public static synchronized Singleton getInstance() { //访问实例对象时创建单例类实例

if (singletonVar == null) {
singletonVar = new Singleton();
}
return singletonVar;
}

}

synchronized关键字的作用是实现线程同步,保证在同一时刻只有一个线程能够访问某个资源。在懒汉式单例类中,getInstance()方法是获取单例对象的唯一入口,因此使用synchronized关键字可以保证在多线程环境下,只有一个线程能够创建单例对象,从而保证单例对象的唯一性。如果不使用synchronized关键字,在多线程环境下,可能会有多个线程同时执行getInstance()方法,从而导致多个单例对象被创建。这将会破坏单例模式的设计意图。

适用场景

  1. 需要频繁实例化对象,然后销毁对象。
  2. 创建对象时开销较大,但又需经常使用该对象。
  3. 有状态的工具类对象。
  4. 频繁访问的数据库或文件对象。

应用示例——用懒汉式单例模式模拟产生美国当今总统对象

image-20231224121647570

  • 设计一个 President单例模式类。该类提供了一个静态方法,供外界获取它的静态实例。
  • SingletonLazy类访问 President单例类来获取 President对象。

SingletonLazy类编程代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SingletonLazy {

public static void main(String[] args) {
President zt1 = President.getInstance();
zt1.getName(); // 输出总统的名字
President zt2 = President.getInstance();
zt2.getName(); // 输出总统的名字
if (zt1 == zt2) {
System.out.println("他们是同一人!");
} else {
System.out.println("他们不是同一人!");
}
}
}

President类编程代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class President {
private static volatile President instance = null; //保证 instance 在所有线程中同步

//private避免类在外部被实例化
private President() {
System.out.println("产生一个总统!");
}

public static synchronized President getInstance() {
//在getInstance方法上加静态锁,避免并发线程访问产生多个实例
if (instance == null) {
instance = new President();
} else {
System.out.println("已经有一个总统,不能产生新总统!");
}
return instance;
}

public void getName() {
System.out.println("我是美国总统:拜登。");
}
}

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class SingletonLazy {

public static void main(String[] args) {
President zt1 = President.getInstance();
zt1.getName(); // 输出总统的名字
President zt2 = President.getInstance();
zt2.getName(); // 输出总统的名字
if (zt1 == zt2) {
System.out.println("他们是同一人!");
} else {
System.out.println("他们不是同一人!");
}
}
}

class President {
private static volatile President instance = null; //保证 instance 在所有线程中同步

//private避免类在外部被实例化
private President() {
System.out.println("产生一个总统!");
}

public static synchronized President getInstance() {
//在getInstance方法上加静态锁,避免并发线程访问产生多个实例
if (instance == null) {
instance = new President();
} else {
System.out.println("已经有一个总统,不能产生新总统!");
}
return instance;
}

public void getName() {
System.out.println("我是美国总统:拜登。");
}
}

运行结果:
image-20231224145907562

模式优缺点

优点:

  • 在内存中一个类只有一个实例,可减少程序占用内存的开销。
  • 避免频繁的创建和销毁实例,可以提高软件程序运行性能。
  • 避免对资源的多重占用。
  • 提供全局访问点,可以共享资源访问。

缺点:

  • 单例模式一般没有接口,扩展困难。如果要扩展功能,需修改原来的代码,这违背开闭原则
  • 在并发编程中,单例模式不利于代码调试,因在单例中的代码没有执行完,不能模拟生成一个新的对象。
  • 单例模式的功能代码通常写在一个类中。如果功能设计不合理,则很容易违背单一职责原则

针对如下民政服务系统的婚姻登记功能设计类图,如何采用饿汉式单例模式实现Java程序编写,并在主程序中输出消息反馈。

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class MarriageRegister {

// 私有静态实例,在类加载时创建
private static final MarriageRegister instance = new MarriageRegister();

// 私有构造方法,防止外部通过new创建多个实例
private MarriageRegister() {
// 初始化操作
}

// 公有静态方法,返回唯一实例
public static MarriageRegister getInstance() {
return instance;
}

// 公有方法,输出消息
public void showMessage() {
System.out.println("Marriage registration is successful.");
}
}

public class Client {

public static void main(String[] args) {
// 获取MarriageRegister的单例实例
MarriageRegister register = MarriageRegister.getInstance();

// 输出消息
register.showMessage();
}
}

运行结果:
image-20231224152645879

结构型模式

结构型模式描述如何将类或对象组成更大的结构实现特定功能特性。它分为类结构型模式和对象结构型模式。前者采用继承机制来组织接口和类,后者采用组合或聚合来组织对象。

结构型模式类型

  • 适配器模式 (Adapter Pattern)
  • 桥接模式 (Bridge Pattern)
  • 装饰器模式 (Decorator Pattern)
  • 外观模式 (Facade Pattern)
  • 组合模式 (Composite Pattern)
  • 享元模式 (Flyweight Pattern)
  • 代理模式 (Proxy Pattern)

适配器模式 (Adapter Pattern)

适配器模式实现不同类接口之间的转换,使接口不兼容的那些类也能一起工作。

模式动机

当利用现有的类库实现系统的新功能开发时,发现当前系统的接口与现有类库规范不兼容。如果重新开发这些类成本又很高,这时可以使用适配器模式来解决现有类的复用问题。

解决主要问题

  • 系统需要使用现有的类,但该类的接口不符合系统的访问需要。

  • 如何通过接口转换,使一个类可以访问另一个类。

模式设计方案

image-20231224133631160

  • 目标接口 (Target) : 当前系统业务所期待使用的接口。
  • 适配者类 (Adaptee):它是被访问和适配的现有组件类.
  • 适配器类 (classAdapter ) : 它是个转换器,通过继承适配者和实现目标接口,让客户端类可通过目标接口访问适配者。

模式解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
package adapter;

//目标接口
interface Target {
public void request();
}

//被适配者类
class Adaptee {
public void specificRequest() {
System.out.println("适配者中的业务代码被调用!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 适配器类
class ClassAdapter extends Adaptee implements Target {
public void request() {
specificRequest();
}
}

// 客户端测试代码
public class ClassAdapterTest {
public static void main(String[] args) {
System.out.println("类适配器模式测试:");
Target target = new ClassAdapter();
target.request();
}
}

运行输出结果:

image-20231224145745091

适用场景

  • 不想修改原有代码而重用现有的类功能。
  • 将一个类的接口转换成客户希望的另外一个接口,使得原本不兼容的那些类|能一起工作。
  • 使用第三方提供的类,但类接口定义和自己要求的接口定义不同。

应用示例

image-20231224135354206

  • 为了实现AudioPlayer播放其他格式的音频文件。需要创建一个实现 MediaPlayer接口的适配器类MediaAdapter。该适配器类使用AdvancedMediaPlayer类的对象来播放所需的媒体文件格式。

  • Audioplayer 使用适配器类MediaAdapter 传递所需的音频类型,它不需要知道能播放所需格式音频的实际类。

  • AdapterPatternDemo 类使用AudioPlayer 类和适配器类MediaAdapter 来播放各种格式文件。

MediaPlayer接口:

1
2
3
4
5
6
package AdapterPattern;

//媒体播放器接口
public interface MediaPlayer {
public void play(String audioType, String fileName);
}

AdvancedMediaPlayer接口:

1
2
3
4
5
6
7
8
package AdapterPattern;

//高级媒体播放器接口
public interface AdvancedMediaPlayer {
public void playVlc(String fileName);

public void playMp4(String fileName);
}

VlcPlayer类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package AdapterPattern;

//创建实现了 AdvancedMediaPlayer 接口的实体类
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("播放 vlc file. Name: " + fileName);
}

@Override
public void playMp4(String fileName) {
//什么也不做
}
}

Mp4Player类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package AdapterPattern;

//创建实现了 AdvancedMediaPlayer 接口的实体类
public class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
//什么也不做
}

@Override
public void playMp4(String fileName) {
System.out.println("播放 mp4 file. Name: " + fileName);
}
}

MediaAdapter类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package AdapterPattern;

public class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer;

public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer = new Mp4Player();
}
}

@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer.playMp4(fileName);
}
}
}

AudioPlayer类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package AdapterPattern;

public class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;

@Override
public void play(String audioType, String fileName) {
// 播放 mp3 音乐文件的内置支持
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file. Name: " + fileName);
}
// mediaAdapter 提供了播放其他文件格式的支持
else if (audioType.equalsIgnoreCase("vlc")
|| audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media. " +
audioType + " format not supported");
}
}
}

AdapterPatternDemo类:

1
2
3
4
5
6
7
8
9
10
11
12
13
package AdapterPattern;

//使用AudioPlayer来播放不同类型的音频格式
public class AdapterPatternDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();

audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}

运行结果:

image-20231224141917166

补:equalsIgnoreCase 是 Java 中的一个方法,用于比较两个字符串是否相等,而忽略它们的大小写。这个方法是 String 类的成员,所以可以对任何字符串对象调用它。

模式优缺点

优点:

  • 客户端通过适配器可以透明地调用目标接口。
  • 程序员不需要修改原有代码而重用现有的适配者类。
  • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
  • 该模式符合开闭原则。

缺点:

  • 适配器编写过程需要结合业务场景全面考虑,可能会增加编程的复杂性。
  • 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得复杂。

分析下面设计类图如何解决新能源汽车的发动机接口?给出Java程序实现该设计功能。

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// Motor接口
interface Motor {
void drive();
}

// 电动电机类
class ElectricMotor {
public void electricDrive() {
System.out.println("Electric Motor drive.");
}
}

// 光电电机类
class OpticalMotor {
public void opticalDrive() {
System.out.println("Optical Motor drive.");
}
}

// 电动电机适配器
class ElectricAdapter implements Motor {
private ElectricMotor emotor;

public ElectricAdapter() {
emotor = new ElectricMotor();
}

public void drive() {
emotor.electricDrive();
}
}

// 光电电机适配器
class OpticalAdapter implements Motor {
private OpticalMotor omotor;

public OpticalAdapter() {
omotor = new OpticalMotor();
}

public void drive() {
omotor.opticalDrive();
}
}

// 读取XML配置文件
class ReadXML {
// 这里提供一个示例方法,用于模拟从XML配置中获取电机对象
public static Object getObject() {
// 这里可以根据实际情况读取XML配置,实例化不同的Motor
// 以下代码仅作为示例
return new ElectricAdapter(); // 或者 return new OpticalAdapter();
}
}

public class MotorAdapterTest {
public static void main(String[] args) {
// 假设ReadXML.getObject()方法返回一个Motor类型的对象
Motor motor = (Motor) ReadXML.getObject();
motor.drive();
}
}

运行结果:
image-20231224152610322

桥接模式 (Bridge Pattern)

桥接模式是一种用于把抽象类与实现类解耦,使得二者都可以实现独立变化的设计模式。它通过在抽象类和实现类之间加入桥接接口,来实现二者的解耦。

模式动机

在一些应用中,某些类具有多个维度的变化,如既可按形状扩展,又可按颜色扩展。如果类采用继承方式实现m种形状和n种颜色进行扩展建模,其子类就有 m×n 种,其扩展较困难。当采用桥接模式可以将抽象类与实现类解耦,使得二者可以独立变化,从而容易实现功能类扩展。

解决问题

  • 如何将抽象类与实现类分离,使它们都可以独立地进行变化。
  • 在抽象类与实现类都有多种变化情况下,如何解决扩展困难问题。

模式设计方案

image-20231224144505930

  • 抽象化角色类 (Abstraction) 维护一个指向Implementor接口的指针。
  • 扩展抽象化角色类(RefinedAbstraction )扩充抽象角色类Abstraction功能。
  • 接口类 (Implementor)定义实现类的接口。
  • 实现类 (ConcretelmpIementor )具体定义实现操作。

模式实现方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package bridge;

public class BridgeTest {
public static void main(String[] args) {
Implementor imple = new ConcreteImplementorA();
Abstraction abs = new RefinedAbstraction(imple);
abs.Operation();
}
}

//实现化接口
interface Implementor {
public void OperationImpl();
}

//具体实现化类
class ConcreteImplementorA implements Implementor {
public void OperationImpl() {
System.out.println("具体实现化(Concrete Implementor)角色被访问");
}
}

//抽象化角色
abstract class Abstraction {
protected Implementor imple;

protected Abstraction(Implementor imple) {
this.imple = imple;
}

public abstract void Operation();
}

//扩充抽象化角色
class RefinedAbstraction extends Abstraction {
protected RefinedAbstraction(Implementor imple) {
super(imple);
}

public void Operation() {
System.out.println("扩充抽象化(Refined Abstraction)角色被访问");
imple.OperationImpl();
}
}

运行结果:
image-20231224145709135

适用场景

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
  • 当一个系统不希望使用继承或多层次继承导致子类个数急剧增加。
  • 当一个系统需要在构件的抽象化类和具体化实现类之间增加更多的灵活性。

应用示例

image-20231224150209823

  • 实现接口 DrawAPI及其实体类RedCircle、GreenCircle。
  • Shape 抽象类将使用DrawAPI 接口对象。Circle为扩展抽象类。
  • BridgePatternDemo 类 使用 Shape类来画出不同颜色的圆。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package BridgePattern;

//画图API接口
interface DrawAPI {
public void drawCircle(int radius, int x, int y);
}

//红色圆圈的具体实现
class RedCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", y: " + y + "]");
}
}

//绿色圆圈的具体实现
class GreenCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", y: " + y + "]");
}
}

//抽象的形状类
abstract class Shape {
protected DrawAPI drawAPI;

protected Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}

public abstract void draw();
}

//圆形类的实现
class Circle extends Shape {
private int x, y, radius;

public Circle(int x, int y, int radius, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}

public void draw() {
drawAPI.drawCircle(radius, x, y);
}
}

//桥接模式的演示类
public class BridgePatternDemo {
public static void main(String[] args) {
Shape redCircle = new Circle(100, 100, 10, new RedCircle());
Shape greenCircle = new Circle(100, 100, 10, new GreenCircle());

redCircle.draw();
greenCircle.draw();
}
}

运行结果:

image-20231224151949438

模式优缺点

优点:

  • 抽象类和实现类的分离有助于降低对实现部分编译时的依赖
  • 可独立地对抽象类和实现类的层次结构进行扩充
  • 实现细节对客户透明
  • 符合开闭原则

缺点:

  • 桥接模式会增加系统的理解与设计难度
  • 要求开发者针对抽象类进行设计与编程

分析如下女士皮包选购功能类图设计如何应用桥接模式?如何编写Java程序实现该功能。

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// Color接口及其具体实现
interface Color {
String getColor();
}

class Yellow implements Color {
public String getColor() {
return "yellow";
}
}

class Red implements Color {
public String getColor() {
return "red";
}
}

// Bag抽象类及其具体实现
abstract class Bag {
protected Color color;

public void setColor(Color color) {
this.color = color;
}

public abstract String getName();
}

class HandBag extends Bag {
public String getName() {
return "HandBag";
}
}

class Wallet extends Bag {
public String getName() {
return "Wallet";
}
}

// BagManage类来展示如何使用桥接模式
public class BagManage {
public static void main(String[] args) {
// 创建具体实现化角色
Color yellow = new Yellow();
Color red = new Red();

// 创建扩充抽象化角色,并桥接具体实现化角色
Bag handBag = new HandBag();
handBag.setColor(yellow); // 设置黄色

Bag wallet = new Wallet();
wallet.setColor(red); // 设置红色

// 操作
System.out.println("The " + handBag.getName() + " is " + handBag.color.getColor());
System.out.println("The " + wallet.getName() + " is " + wallet.color.getColor());
}
}

运行结果:
image-20231224153833943

行为型模式

行为型模式用于解决程序在运行时存在的复杂流程控制,如多个对象之间相互协作完成功能任务,以及如何分配职责。

行为型模式类型

  • 责任链模式 (Chain of Responsibility Pattern)
  • 命令模式 (Command Pattern)
  • 解释器模式 (Interpreter Pattern)
  • 迭代器模式 (lterator Pattern)
  • 中介者模式(Mediator Pattern)
  • 观察者模式(Observer Pattern
  • 备忘录模式(Memento Pattern)

责任链模式 (Chain of Responsibility Pattern)

责任链模式是一种通过对象链进行请求处理的设计模式。

模式动机

为了避免请求发送者与多个处理者耦合在一起,仅需将请求发生给责任链首个对象,后续在该链的对象中传递处理,直到有某对象处理它为止。

解决问题

  • 客户请求发送到对象责任链后,如何做到无须关心请求的处理细节和请求在对象责任链上的传递过程。
  • 对象责任链如何将请求的发送者与处理者进行解耦。

模式设计方案

image-20231224154811912

  • 抽象处理者类 (Handler ) : 定义一个处理请求的接口,包含抽象处理方法( handleRequest()) 和一个后继连接(next)。
  • 具体处理者类(ConcreteHandler ) : 实现抽象处理者的处理方法,判断自己能否处理本次请求。如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

image-20231224155200556

模式实现方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package chainOfResponsibility;

public class ChainOfResponsibilityPattern {
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
handler1.setNext(handler2);
handler1.handleRequest("two");
}
}

abstract class Handler {
private Handler next;

public void setNext(Handler next) {
this.next = next;
}

public Handler getNext() {
return next;
}

public abstract void handleRequest(String request);
}

class ConcreteHandler1 extends Handler {
public void handleRequest(String request) {
if (request.equals("one")) {
System.out.println("具体处理者1负责处理请求!");
} else {
if (getNext() != null) {
getNext().handleRequest(request);
} else {
System.out.println("没有人处理该请求!");
}
}
}
}

class ConcreteHandler2 extends Handler {
public void handleRequest(String request) {
if (request.equals("two")) {
System.out.println("具体处理者2负责处理请求!");
} else {
if (getNext() != null) {
getNext().handleRequest(request);
} else {
System.out.println("没有人处理该请求!");
}
}
}
}

运行结果:
image-20231224155801605

适用场景

  1. 有多个对象处理同一个请求,具体哪个对象处理该请求由运行时刻根据消息内容自动确定。
  2. 在不明确指定接收者的情况下,向多个对象中的首个对象提交请求。
  3. 可动态指定一组对象处理请求,或添加新的处理者。

应用示例

image-20231224160009340

  • 创建抽象类AbstractLogger,记录日志信息。
  • 创建三种日志类型的记录器,分别继承AbstractLogger抽象类。
  • 每个记录器接收消息后,判断该消息是否属于自己的类型。如果是则相应地打印出来。否则将不打印,并把消息传给下一个记录器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package ChainResponsibilityPattern;
// 创建抽象的记录器类

abstract class AbstractLogger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;

protected int level;
// 责任链中的下一个元素
protected AbstractLogger nextLogger;

public void setNextLogger(AbstractLogger nextLogger) {
this.nextLogger = nextLogger;
}

public void logMessage(int level, String message) {
if (this.level <= level) {
write(message);
}
if (nextLogger != null) {
nextLogger.logMessage(level, message);
}
}

abstract protected void write(String message);
}

// 创建扩展了该记录器类的实体类
class ConsoleLogger extends AbstractLogger {
public ConsoleLogger(int level) {
this.level = level;
}

@Override
protected void write(String message) {
System.out.println("Standard Console::Logger: " + message);
}
}

class ErrorLogger extends AbstractLogger {
public ErrorLogger(int level) {
this.level = level;
}

@Override
protected void write(String message) {
System.out.println("Error Console::Logger: " + message);
}
}

class FileLogger extends AbstractLogger {
public FileLogger(int level) {
this.level = level;
}

@Override
protected void write(String message) {
System.out.println("File::Logger: " + message);
}
}

// 使用责任链模式的不同记录器记录消息
public class ChainPatternDemo {
private static AbstractLogger getChainOfLoggers() {
AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);

errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);

return errorLogger;
}

public static void main(String[] args) {
AbstractLogger loggerChain = getChainOfLoggers();

loggerChain.logMessage(AbstractLogger.INFO, "这是一个信息日志信息。");
loggerChain.logMessage(AbstractLogger.DEBUG, "这是一个 debug 日志信息。");
loggerChain.logMessage(AbstractLogger.ERROR, "这是一个 error 日志信息。");
}
}

运行结果:
image-20231224161959045

模式优缺点

优点:

  • 降低了请求对象与处理对象之间的耦合度。一个请求对象无须知道到底是哪一个对象理其请求以及链的结构。

  • 可以根据需要增加新的请求处理类,满足开闭原则

  • 当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。

  • 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者if…else 语句。

  • 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点:

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。

  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。

  • 职责链建立的合理性要靠客户端来保证,增加了客户端编程的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

分析如下请假条审批模块功能类图如何应用责任链模式设计?如何编写Java程序。

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 领导者是抽象处理者
abstract class Leader {
private Leader next;

public void setNext(Leader next) {
this.next = next;
}

public Leader getNext() {
return next;
}

// 抽象处理请求的方法
public abstract void handleRequest(int LeaveDays);
}

// 具体处理者1:班主任
class ClassAdviser extends Leader {
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 2) { // 班主任可以批准最多2天的请假
System.out.println("班主任批准了 " + LeaveDays + " 天的请假。");
} else if (next != null) {
next.handleRequest(LeaveDays);
}
}
}

// 具体处理者2:系主任
class DepartmentHead extends Leader {
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 5) { // 系主任可以批准最多5天的请假
System.out.println("系主任批准了 " + LeaveDays + " 天的请假。");
} else if (next != null) {
next.handleRequest(LeaveDays);
}
}
}

// 具体处理者3:院长
class Dean extends Leader {
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 10) { // 院长可以批准最多10天的请假
System.out.println("院长批准了 " + LeaveDays + " 天的请假。");
} else {
System.out.println("请假请求 " + LeaveDays + " 天被拒绝。");
}
}
}

// 客户端类
public class LeaveApprovalTest {
public static void main(String[] args) {
// 设置责任链
Leader teacher1 = new ClassAdviser();
Leader teacher2 = new DepartmentHead();
Leader teacher3 = new Dean();

teacher1.setNext(teacher2);
teacher2.setNext(teacher3);

// 发起请求
teacher1.handleRequest(8); // 这个请求将由院长处理
}
}

客户端类也可按如下方式书写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class LeaveApprovalTest {

private static Leader getChainOfLeaders() {
// 创建责任链
Leader classAdviser = new ClassAdviser();
Leader departmentHead = new DepartmentHead();
Leader dean = new Dean();

// 设置责任链
classAdviser.setNext(departmentHead);
departmentHead.setNext(dean);

// 返回链的起始点
return classAdviser;
}

public static void main(String[] args) {
Leader chainOfLeaders = getChainOfLeaders();

// 发起请求
chainOfLeaders.handleRequest(8); // 这个请求将由院长处理
}
}

班主任可以批准最多2天的请假,系主任可以批准最多5天的请假,院长可以批准最多10天的请假。如果有一个请求是请8天假,班主任会将它传递给系主任,系主任再传递给院长进行批准。如果请求超出了批准能力,就会被拒绝。

中介者模式(Mediator Pattern)

中介者模式是一种通过中介对象来封装若干对象之间的交互,实现对象之间的松耦合,并且可以独立地改变它们之间交互的设计模式。

模式动机

在一些应用中,多个对象之间存在较复杂的网状交互关系。如果把这种’网状结构”改为“星形结构”,可降低它们之间的“耦合性”。这时只要找一个“中介者”来实现。

解决主要问题

  • 如何解决多个对象之间存在大量的关联关系。
  • 若一个对象发生改变,如何跟踪与之相关联的对象,并做出相应的处理。

模式设计方案

image-20231224165400386

  • 抽象中介者 (Mediator) : 提供了同事对象注册与转发同事对象信息的抽象方法。

  • 具体中介者 (Concrete Mediator) :实现抽象中介者类,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系。

  • 抽象同事类(Colleague) : 定义同事类的抽象方法,引用中介者对象,实现同事类的公共功能。

  • 具体同事类 (Concrete Colleague) : 抽象同事类的实现者,当同事对象交互时由中介者对象负责转发之间的交互。

模式实现方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package mediator;

import java.util.*;

// 中介者模式
public class MediatorPattern {
public static void main(String[] args) {
Mediator md = new ConcreteMediator();
Colleague c1, c2;
c1 = new ConcreteColleague1();
c2 = new ConcreteColleague2();
md.register(c1);
md.register(c2);
c1.send();
System.out.println("---------------");
c2.send();
}
}

// 抽象中介者
abstract class Mediator {
public abstract void register(Colleague colleague);

public abstract void relay(Colleague cl); // 转发
}

// 具体中介者
class ConcreteMediator extends Mediator {
private List<Colleague> colleagues = new ArrayList<Colleague>();

public void register(Colleague colleague) {
if (!colleagues.contains(colleague)) {
colleagues.add(colleague);
colleague.setMedium(this);
}
}

public void relay(Colleague cl) {
for (Colleague ob : colleagues) {
if (!ob.equals(cl)) {
((Colleague) ob).receive();
}
}
}
}

// 抽象同事类
abstract class Colleague {
protected Mediator mediator;

public void setMedium(Mediator mediator) {
this.mediator = mediator;
}

public abstract void receive();

public abstract void send();
}

// 具体同事类1
class ConcreteColleague1 extends Colleague {
public void receive() {
System.out.println("具体同事类1收到请求。");
}

public void send() {
System.out.println("具体同事类1发出请求。");
mediator.relay(this); // 请求中介者转发
}
}

// 具体同事类2
class ConcreteColleague2 extends Colleague {
public void receive() {
System.out.println("具体同事类2收到请求。");
}

public void send() {
System.out.println("具体同事类2发出请求。");
mediator.relay(this); // 请求中介者转发
}
}

运行结果:

image-20231224171143371

适用场景

  1. 当对象之间存在复杂的网状结构关系而导致通信关系复杂。
  2. 通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

应用示例

image-20231224171439525

  • 多个用户可以通过聊天室进行消息交流聊天室向所有的用户显示消息。

  • 创建两个类 ChatRoom 和 User。

  • User 对象 使用 ChatRoom 对象的showMessage()方法来分享他们的消息。

  • MediatorPatternDemo演示类使用 User对象来显示他们之间的通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package MediatorPattern;

import java.util.Date;

// 聊天室类充当中介者
class ChatRoom {
public static void showMessage(User user, String message) {
System.out.println(new Date().toString() + " [" + user.getName() + "]: " + message);
}
}

// 用户类
class User {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public User(String name) {
this.name = name;
}

public void sendMessage(String message) {
ChatRoom.showMessage(this, message);
}
}

// 中介者模式演示
public class MediatorPatternDemo {
public static void main(String[] args) {
User robert = new User("Robert");
User john = new User("John");

robert.sendMessage("Hi! John!");
john.sendMessage("Hello! Robert!");
}
}

运行示例:
image-20231224172051032

模式优缺点

优点:

  • 类之间各司其职,符合迪米特法则。

  • 降低了对象之间的耦合性,使得对象易于独立地被复用。

  • 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

缺点:

  • 中介者模式将原本多个对象直接的相互依赖变成了中介者和多个对象之间的依赖关系。
  • 当交互的对象越多时,中介者就会越臃肿,变得复杂且难以维护。

分析如下“房地产交流”模块功能类图如何应用中介者模式设计?给出Java程序。

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import java.util.ArrayList;
import java.util.List;

// 中介者接口
interface Medium {
void register(Customer member);

void relay(String from, String ad);
}

// 房地产中介
class EstateMedium implements Medium {
private List<Customer> members = new ArrayList<>();

@Override
public void register(Customer member) {
if (!members.contains(member)) {
members.add(member);
member.setMedium(this);
}
}

@Override
public void relay(String from, String ad) {
for (Customer member : members) {
if (!member.getName().equals(from)) {
member.receive(from, ad);
}
}
}
}

// 客户类
abstract class Customer {
protected Medium medium;
protected String name;

public Customer(String name) {
this.name = name;
}

public abstract void send(String ad);

public abstract void receive(String from, String ad);

public String getName() {
return name;
}

public void setMedium(Medium medium) {
this.medium = medium;
}
}

// 卖方类
class Seller extends Customer {
public Seller(String name) {
super(name);
}

@Override
public void send(String ad) {
System.out.println("Seller [" + this.name + "] sends message: " + ad);
medium.relay(this.name, ad);
}

@Override
public void receive(String from, String ad) {
System.out.println("Seller [" + this.name + "] received message from " + from + ": " + ad);
}
}

// 买方类
class Buyer extends Customer {
public Buyer(String name) {
super(name);
}

@Override
public void send(String ad) {
System.out.println("Buyer [" + this.name + "] sends message: " + ad);
medium.relay(this.name, ad);
}

@Override
public void receive(String from, String ad) {
System.out.println("Buyer [" + this.name + "] received message from " + from + ": " + ad);
}
}

// 中介者模式演示
public class MediatorPattern {
public static void main(String[] args) {
Medium medium = new EstateMedium();
Customer seller = new Seller("Alice");
Customer buyer = new Buyer("Bob");

medium.register(seller);
medium.register(buyer);

seller.send("House for sale!");
buyer.send("Looking for a house!");
}
}

运行结果:
image-20231224174229978

课堂作业与作业练习

一、单选题

1.哪种设计原则要求面向接口编程,而不要面向实现编程?(C)

A.开闭原则

B.里氏替换原则

C.依赖倒置原则

D.接口分离原则

2.哪种设计原则要求没有直接关系的类就不能直接相互调用?(B)

A.里氏替换原则

B.迪米特法则

C. 依赖倒置原则

D.开闭原则

3.下面哪个不是发现类的方法?(C)

A. CRC方法

B.用例驱动法

C.头脑风暴

D.公共类模式法

4.子类组合来自超类的特征,并重载部分继承来的特征,该继承称为什么?(C)

A.扩展继承

B.方便继承

C.限制继承

D.以上都不是

5.下面哪一个操作符用于定义条件片段?(C)

A. opt

B. para

C. alt

D. loop

二、判断题

1.单例模式属于设计模式的行为模式类别。(×)

2.适配器模式可以解决不同模块的类接口转换问题。(√)

3.桥接模式是一种用于把抽象部分与实现部分解耦的设计模式。(√)

4.责任链模式要求请求发送者与多个请求处理者直接交互。(×)

5.中介者模式的动机用于降低多个对象之间存在较复杂的关系。(√)

6.处于相同状态的同类的不同对象对同一事件的反应往往是一样的,而处于不同状态的同一对象则对同一事件会做出不同反应。(√)

7.只要将包中元素的可见性设为公共的,则其他包就可以访问它。(×)

8.聚合与泛化都是面向对象系统支持功能复用的强大技术。(×)

9.在UML构件图中,需要定义消息来描述构件之间的联系。(×)

10.所有对象都通过类来描述,所有类都具有对象。(×)

补充:

6.处于相同状态的对象对同一事件具有同样方式的反应,所以当给定状态下的多个对象当接受到相同事件时会执行相同的动作,例如:当一个在线视频播放器处于”播放中”状态时,对于”暂停”按钮的点击事件,所有这类播放器对象都会执行相同的动作,即暂停当前视频。无论哪个播放器实例,只要它正在播放视频,点击”暂停”按钮都会导致视频停止播放。然而处于不同状态下的对象会通过不同的动作对同一事件做出不同的反应。例如,当自动答复机处于处理事务状态或空闲状态时会对取消键做出不同的反应。

7.虽然将类或成员的可见性设为公共(public)可以让其他包中的类访问这些元素,但这并不是唯一的条件。其他包中的类还需要正确地导入包含公共元素的包。

8.这道题网上搜出来都是对的,答案给的错,后面再看看吧。

10.所有对象都是由类来描述的,这是正确的;但不是所有类都具有对象实例。有些类可能从未被实例化,尤其是抽象类和工具类(可能只有静态方法和成员)。

三、填空题

(状态机图)通过对对象的各种状态建立模型来描述对象随时间变化的动态行为,并且它是以独立的对象为中心进行描述的。

在UML类图中,类用矩形图符来表示,这个矩形由3个部分组成,分别是类型名、(属性)和操作。

UML中的交互图包括顺序图和(通信图)。

UML中顺序图表示为二维图,纵向是对象,横向代表参与交互对象之间的(消息)。

状态机图由对象的状态和连接这些状态的(转换)组成。