电子科大软件系统架构设计——设计模式
设计模式概述
设计模式的背景
- 设计面向对象软件比较困难,而设计可以复用的面向对象软件更加困难
- 不是解决任何问题都需要从头做起,最好能复用以往的设计方案经验
- 面向对象软件设计经验需要有一定的模式记录下来,以提供给其他设计者使用,这就产生了设计模式。
什么是设计模式
设计模式 (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应用的配置对象
模式解决问题
如何让系统只允许唯一的实例进行服务资源访问,从而解决多实例模式带来的系统开销问题。
解决问题思想:
- 单例模式类只允许创建一个实例对象;
- 该实例对象必须在单例模式类初始化时自行创建;
- 单例模式类对外仅提供一个访问该单例的全局访问点。
模式设计方案
- 采用一个类仅能创建一个实例对象。
- 该类需自行创建这个实例对象。
- 该类提供全局公开方法让外部来获取这个实例对象。

模式实现方案
饿汉式单例类:在单例类被初始化加载时,就实例化一个对象交给自己的引用。
// 饿汉式单例类
public class Singleton {private static Singleton singletonVar = new Singleton();//在定义变量时就创建对象private Singleton() {} //私有的构造方法public static Singleton getInstance() { //静态公有方法返回创建的对象return singletonVar;}}
懒汉式单例类:在访问实例对象时才实例化对象。
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()方法,从而导致多个单例对象被创建。这将会破坏单例模式的设计意图。
适用场景
- 需要频繁实例化对象,然后销毁对象。
- 创建对象时开销较大,但又需经常使用该对象。
- 有状态的工具类对象。
- 频繁访问的数据库或文件对象。
应用示例——用懒汉式单例模式模拟产生美国当今总统对象

- 设计一个 President单例模式类。该类提供了一个静态方法,供外界获取它的静态实例。
- SingletonLazy类访问 President单例类来获取 President对象。
SingletonLazy类编程代码:
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类编程代码:
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("我是美国总统:拜登。");}
}
完整代码:
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("我是美国总统:拜登。");}
}
运行结果:

模式优缺点
优点:
- 在内存中一个类只有一个实例,可减少程序占用内存的开销。
- 避免频繁的创建和销毁实例,可以提高软件程序运行性能。
- 避免对资源的多重占用。
- 提供全局访问点,可以共享资源访问。
缺点:
- 单例模式一般没有接口,扩展困难。如果要扩展功能,需修改原来的代码,这违背开闭原则。
- 在并发编程中,单例模式不利于代码调试,因在单例中的代码没有执行完,不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中。如果功能设计不合理,则很容易违背单一职责原则。
针对如下民政服务系统的婚姻登记功能设计类图,如何采用饿汉式单例模式实现Java程序编写,并在主程序中输出消息反馈。

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();}
}
运行结果:

结构型模式
结构型模式描述如何将类或对象组成更大的结构实现特定功能特性。它分为类结构型模式和对象结构型模式。前者采用继承机制来组织接口和类,后者采用组合或聚合来组织对象。
结构型模式类型
- 适配器模式 (Adapter Pattern)
- 桥接模式 (Bridge Pattern)
- 装饰器模式 (Decorator Pattern)
- 外观模式 (Facade Pattern)
- 组合模式 (Composite Pattern)
- 享元模式 (Flyweight Pattern)
- 代理模式 (Proxy Pattern)
适配器模式 (Adapter Pattern)
适配器模式实现不同类接口之间的转换,使接口不兼容的那些类也能一起工作。
模式动机
当利用现有的类库实现系统的新功能开发时,发现当前系统的接口与现有类库规范不兼容。如果重新开发这些类成本又很高,这时可以使用适配器模式来解决现有类的复用问题。
解决主要问题
-
系统需要使用现有的类,但该类的接口不符合系统的访问需要。
-
如何通过接口转换,使一个类可以访问另一个类。
模式设计方案

- 目标接口 (Target) : 当前系统业务所期待使用的接口。
- 适配者类 (Adaptee):它是被访问和适配的现有组件类.
- 适配器类 (classAdapter ) : 它是个转换器,通过继承适配者和实现目标接口,让客户端类可通过目标接口访问适配者。
模式解决方案
package adapter;//目标接口
interface Target {public void request();
}//被适配者类
class Adaptee {public void specificRequest() {System.out.println("适配者中的业务代码被调用!");}
}
// 适配器类
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();}
}
运行输出结果:

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

-
为了实现AudioPlayer播放其他格式的音频文件。需要创建一个实现 MediaPlayer接口的适配器类MediaAdapter。该适配器类使用AdvancedMediaPlayer类的对象来播放所需的媒体文件格式。
-
Audioplayer 使用适配器类MediaAdapter 传递所需的音频类型,它不需要知道能播放所需格式音频的实际类。
-
AdapterPatternDemo 类使用AudioPlayer 类和适配器类MediaAdapter 来播放各种格式文件。
MediaPlayer接口:
package AdapterPattern;//媒体播放器接口
public interface MediaPlayer {public void play(String audioType, String fileName);
}
AdvancedMediaPlayer接口:
package AdapterPattern;//高级媒体播放器接口
public interface AdvancedMediaPlayer {public void playVlc(String fileName);public void playMp4(String fileName);
}
VlcPlayer类:
package AdapterPattern;//创建实现了 AdvancedMediaPlayer 接口的实体类
public class VlcPlayer implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {System.out.println("播放 vlc file. Name: " + fileName);}@Overridepublic void playMp4(String fileName) {//什么也不做}
}
Mp4Player类:
package AdapterPattern;//创建实现了 AdvancedMediaPlayer 接口的实体类
public class Mp4Player implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {//什么也不做}@Overridepublic void playMp4(String fileName) {System.out.println("播放 mp4 file. Name: " + fileName);}
}
MediaAdapter类:
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();}}@Overridepublic void play(String audioType, String fileName) {if (audioType.equalsIgnoreCase("vlc")) {advancedMusicPlayer.playVlc(fileName);} else if (audioType.equalsIgnoreCase("mp4")) {advancedMusicPlayer.playMp4(fileName);}}
}
AudioPlayer类:
package AdapterPattern;public class AudioPlayer implements MediaPlayer {MediaAdapter mediaAdapter;@Overridepublic 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类:
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");}
}
运行结果:

补:equalsIgnoreCase 是 Java 中的一个方法,用于比较两个字符串是否相等,而忽略它们的大小写。这个方法是 String 类的成员,所以可以对任何字符串对象调用它。
模式优缺点
优点:
- 客户端通过适配器可以透明地调用目标接口。
- 程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
- 该模式符合开闭原则。
缺点:
- 适配器编写过程需要结合业务场景全面考虑,可能会增加编程的复杂性。
- 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得复杂。
分析下面设计类图如何解决新能源汽车的发动机接口?给出Java程序实现该设计功能。

// 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();}
}
运行结果:

桥接模式 (Bridge Pattern)
桥接模式是一种用于把抽象类与实现类解耦,使得二者都可以实现独立变化的设计模式。它通过在抽象类和实现类之间加入桥接接口,来实现二者的解耦。
模式动机
在一些应用中,某些类具有多个维度的变化,如既可按形状扩展,又可按颜色扩展。如果类采用继承方式实现m种形状和n种颜色进行扩展建模,其子类就有 m×n 种,其扩展较困难。当采用桥接模式可以将抽象类与实现类解耦,使得二者可以独立变化,从而容易实现功能类扩展。
解决问题
- 如何将抽象类与实现类分离,使它们都可以独立地进行变化。
- 在抽象类与实现类都有多种变化情况下,如何解决扩展困难问题。
模式设计方案

- 抽象化角色类 (Abstraction) 维护一个指向Implementor接口的指针。
- 扩展抽象化角色类(RefinedAbstraction )扩充抽象角色类Abstraction功能。
- 接口类 (Implementor)定义实现类的接口。
- 实现类 (ConcretelmpIementor )具体定义实现操作。
模式实现方案
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();}
}
运行结果:

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

- 实现接口 DrawAPI及其实体类RedCircle、GreenCircle。
- Shape 抽象类将使用DrawAPI 接口对象。Circle为扩展抽象类。
- BridgePatternDemo 类 使用 Shape类来画出不同颜色的圆。
package BridgePattern;//画图API接口
interface DrawAPI {public void drawCircle(int radius, int x, int y);
}//红色圆圈的具体实现
class RedCircle implements DrawAPI {@Overridepublic 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 {@Overridepublic 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();}
}
运行结果:

模式优缺点
优点:
- 抽象类和实现类的分离有助于降低对实现部分编译时的依赖
- 可独立地对抽象类和实现类的层次结构进行扩充
- 实现细节对客户透明
- 符合开闭原则
缺点:
- 桥接模式会增加系统的理解与设计难度
- 要求开发者针对抽象类进行设计与编程
分析如下女士皮包选购功能类图设计如何应用桥接模式?如何编写Java程序实现该功能。

// 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());}
}
运行结果:

行为型模式
行为型模式用于解决程序在运行时存在的复杂流程控制,如多个对象之间相互协作完成功能任务,以及如何分配职责。
行为型模式类型
- 责任链模式 (Chain of Responsibility Pattern)
- 命令模式 (Command Pattern)
- 解释器模式 (Interpreter Pattern)
- 迭代器模式 (lterator Pattern)
- 中介者模式(Mediator Pattern)
- 观察者模式(Observer Pattern
- 备忘录模式(Memento Pattern)
责任链模式 (Chain of Responsibility Pattern)
责任链模式是一种通过对象链进行请求处理的设计模式。
模式动机
为了避免请求发送者与多个处理者耦合在一起,仅需将请求发生给责任链首个对象,后续在该链的对象中传递处理,直到有某对象处理它为止。
解决问题
- 客户请求发送到对象责任链后,如何做到无须关心请求的处理细节和请求在对象责任链上的传递过程。
- 对象责任链如何将请求的发送者与处理者进行解耦。
模式设计方案

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

模式实现方案
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("没有人处理该请求!");}}}
}
运行结果:

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

- 创建抽象类AbstractLogger,记录日志信息。
- 创建三种日志类型的记录器,分别继承AbstractLogger抽象类。
- 每个记录器接收消息后,判断该消息是否属于自己的类型。如果是则相应地打印出来。否则将不打印,并把消息传给下一个记录器。
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;}@Overrideprotected void write(String message) {System.out.println("Standard Console::Logger: " + message);}
}class ErrorLogger extends AbstractLogger {public ErrorLogger(int level) {this.level = level;}@Overrideprotected void write(String message) {System.out.println("Error Console::Logger: " + message);}
}class FileLogger extends AbstractLogger {public FileLogger(int level) {this.level = level;}@Overrideprotected 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 日志信息。");}
}
运行结果:

模式优缺点
优点:
-
降低了请求对象与处理对象之间的耦合度。一个请求对象无须知道到底是哪一个对象理其请求以及链的结构。
-
可以根据需要增加新的请求处理类,满足开闭原则
-
当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
-
责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者if…else 语句。
-
责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点:
-
不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
-
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
-
职责链建立的合理性要靠客户端来保证,增加了客户端编程的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
分析如下请假条审批模块功能类图如何应用责任链模式设计?如何编写Java程序。

// 领导者是抽象处理者
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 {@Overridepublic void handleRequest(int LeaveDays) {if (LeaveDays <= 2) { // 班主任可以批准最多2天的请假System.out.println("班主任批准了 " + LeaveDays + " 天的请假。");} else if (next != null) {next.handleRequest(LeaveDays);}}
}// 具体处理者2:系主任
class DepartmentHead extends Leader {@Overridepublic void handleRequest(int LeaveDays) {if (LeaveDays <= 5) { // 系主任可以批准最多5天的请假System.out.println("系主任批准了 " + LeaveDays + " 天的请假。");} else if (next != null) {next.handleRequest(LeaveDays);}}
}// 具体处理者3:院长
class Dean extends Leader {@Overridepublic 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); // 这个请求将由院长处理}
}
客户端类也可按如下方式书写:
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)
中介者模式是一种通过中介对象来封装若干对象之间的交互,实现对象之间的松耦合,并且可以独立地改变它们之间交互的设计模式。
模式动机
在一些应用中,多个对象之间存在较复杂的网状交互关系。如果把这种’网状结构"改为“星形结构”,可降低它们之间的“耦合性”。这时只要找一个“中介者”来实现。
解决主要问题
- 如何解决多个对象之间存在大量的关联关系。
- 若一个对象发生改变,如何跟踪与之相关联的对象,并做出相应的处理。
模式设计方案

-
抽象中介者 (Mediator) : 提供了同事对象注册与转发同事对象信息的抽象方法。
-
具体中介者 (Concrete Mediator) :实现抽象中介者类,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系。
-
抽象同事类(Colleague) : 定义同事类的抽象方法,引用中介者对象,实现同事类的公共功能。
-
具体同事类 (Concrete Colleague) : 抽象同事类的实现者,当同事对象交互时由中介者对象负责转发之间的交互。
模式实现方案
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); // 请求中介者转发}
}
运行结果:

适用场景
- 当对象之间存在复杂的网状结构关系而导致通信关系复杂。
- 通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
应用示例

-
多个用户可以通过聊天室进行消息交流聊天室向所有的用户显示消息。
-
创建两个类 ChatRoom 和 User。
-
User 对象 使用 ChatRoom 对象的showMessage()方法来分享他们的消息。
-
MediatorPatternDemo演示类使用 User对象来显示他们之间的通信。
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!");}
}
运行示例:

模式优缺点
优点:
-
类之间各司其职,符合迪米特法则。
-
降低了对象之间的耦合性,使得对象易于独立地被复用。
-
将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
缺点:
- 中介者模式将原本多个对象直接的相互依赖变成了中介者和多个对象之间的依赖关系。
- 当交互的对象越多时,中介者就会越臃肿,变得复杂且难以维护。
分析如下“房地产交流”模块功能类图如何应用中介者模式设计?给出Java程序。

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<>();@Overridepublic void register(Customer member) {if (!members.contains(member)) {members.add(member);member.setMedium(this);}}@Overridepublic 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);}@Overridepublic void send(String ad) {System.out.println("Seller [" + this.name + "] sends message: " + ad);medium.relay(this.name, ad);}@Overridepublic 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);}@Overridepublic void send(String ad) {System.out.println("Buyer [" + this.name + "] sends message: " + ad);medium.relay(this.name, ad);}@Overridepublic 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!");}
}
运行结果:

课堂作业与作业练习
一、单选题
1.哪种设计原则要求面向接口编程,而不要面向实现编程?©
A.开闭原则
B.里氏替换原则
C.依赖倒置原则
D.接口分离原则
2.哪种设计原则要求没有直接关系的类就不能直接相互调用?(B)
A.里氏替换原则
B.迪米特法则
C. 依赖倒置原则
D.开闭原则
3.下面哪个不是发现类的方法?©
A. CRC方法
B.用例驱动法
C.头脑风暴
D.公共类模式法
4.子类组合来自超类的特征,并重载部分继承来的特征,该继承称为什么?©
A.扩展继承
B.方便继承
C.限制继承
D.以上都不是
5.下面哪一个操作符用于定义条件片段?©
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中顺序图表示为二维图,纵向是对象,横向代表参与交互对象之间的(消息)。
状态机图由对象的状态和连接这些状态的(转换)组成。
相关文章:
电子科大软件系统架构设计——设计模式
设计模式概述 设计模式的背景 设计面向对象软件比较困难,而设计可以复用的面向对象软件更加困难不是解决任何问题都需要从头做起,最好能复用以往的设计方案经验面向对象软件设计经验需要有一定的模式记录下来,以提供给其他设计者使用&#…...
ubuntu20 安装缺失的字体
在/usr/share/fonts创建文件夹winfonts sudo mkdir winfonts 下载缺失的字体后,复制命令到对应的文件夹。 刷新字体库 sudo mkfontscale sudo mkfontdir sudo fc-cache...
2023年12月27日学习记录_加入噪声
目录 1、今日计划学习内容2、今日学习内容1、add noise to audio clipssignal to noise ratio(SNR)加入 additive white gaussian noise(AWGN)加入 real world noises 2、使用kaggel上的一个小demo:CNN模型运行时出现的问题调整采样率时出现bug 3、明确90dB下能否声…...
Java面试题86-95
86. Java代码查错(4)public class Something { public int addOne(final int x) { return x; }}此代码有错误吗?答案: 错。int x被修饰成final,意味着x不能在addOne method中被修改。87. Java代码查错(5&…...
看完谁再说搞不定上下角标?
一、需求 开发中有一些需要用到上下角标的地方,比如说化学式、数学式、注释。。。除了可以使用上下角标的标签,还可以通过css样式和CV大法实现,以下是具体实现方式。 二、实现方法 (1)标签写法: <sup…...
在 Python 中使用装饰器decorator的 7 个层次
在 Python 中使用装饰器的 7 个层次(7 Levels of Using Decorators in Python) 文章目录 在 Python 中使用装饰器的 7 个层次(7 Levels of Using Decorators in Python)导言Level 0: 了解基本概念Basic Concepts和用法Usages什么是装饰器decorator?我们为什么需要装…...
Vue.js项目部署至Linux服务器的详细步骤
引言 在现代Web开发中,Vue.js作为一款流行的前端框架,为开发者提供了灵活且高效的工具。然而,在将Vue.js项目成功部署到Linux服务器上,可能需要一些额外的步骤和注意事项。本文将深入介绍在Linux服务器上部署Vue.js项目的详细步骤…...
Java三层架构/耦合/IOC/DI
一.三层架构 controller/web 控制层。接收前端发送的请求,对请求进行处理,并响应数据。 service 业务逻辑层,处理具体的业务逻辑。 dao 数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、…...
[调试]stm32使用过程debug记录,持续更新ing
遇到的bug:无法在串口助手接收到stm32向主机输出的数据,串口-USB RX灯不闪烁; 分析:闪烁灯实际上为一个二极管,CH 插入电脑USB接口时,RX处于高电平,当数据传输时,拉低电平导致其闪烁…...
知识付费小程序如何搭建?
随着互联网的发展和人们对知识的渴求,知识付费行业正逐渐崭露头角。而其中,知识付费小程序因其便捷性、个性化等特点,成为了越来越多人的首选。那么,如何搭建一个知识付费小程序呢?本文将为你揭秘从零到一的全过程&…...
springboot整合minio做文件存储
一,minio介绍 MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小&…...
拥抱鸿蒙 - 在展讯T606平台上的探索与实践
前 言 自OpenHarmony 问世后受到了社会各界的广泛关注,OpenHarmony 的生态系统在如火如荼的发展。 酷派作为一家积极拥抱变化的公司,经过一段时间的探索与实践,成功实现将OpenHarmony 系统接入到展讯平台上,我们相信这是一个重要…...
nginx源码分析-1
使用gdb查看函数上下文: gdb attach nginx的work线程 监听端口状态时: 断点打在ngx_http_process_request 并通过浏览器触发请求时:...
超分之SRGAN
Photo-Realistic Single Image Super-Resolution Using a Generative Adversarial Network使用生成对抗网络的逼真单图像超分辨率一作:Christian Ledig是Twitter2017年的一篇论文。 文章目录 0. 摘要1. 引言1.1 相关工作1.1.1 介绍了SR技术的发展历程1.1.2 介绍了SR…...
Illustrator脚本 #015 自动角线
这是一个在画板上自动生成辅助线和角线的脚本,只要单击最右边按钮运行脚本即可。 绿色的为参考线及出血线。 #target "Illustrator" var settings = {addTrim : true,addBleedGuide : true,addCenterGuide : true,addCover : false,overlapAlert : false,trimma…...
使用Vite创建React + TypeScript(pro和mobile,含完整的空项目结构资源可供下载)
PC端 安装指令: npm create vitelatest react-ts-pro -- --template react-tsVite是一个框架无关的前端工具链,可以快速的生成一个React TS的开发环境,并且可以提供快速的开发体验说明: 1. npm create vitelatest固定写法&#…...
第一次记录QPSK,BSPK,MPSK,QAM—MATLAB实现
最近有偶然的机会学习了一次QPSK防止以后忘记又得找资料,这里就详细的记录一下 基于 QPSK 的通信系统如图 1 所示,QPSK 调制是目前最常用的一种卫星数字和数 字集群信号调制方式,它具有较高的频谱利用率、较强的抗干扰性、在电路上实现也较为…...
每周一算法:区间覆盖
问题描述 给定 N N N个闭区间 [ a i , b i ] [a_i,b_i] [ai,bi],以及一个线段区间 [ s , t ] [s,t] [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。 输出最少区间数,如果无法完全覆盖则输出 − 1 -1 −1。 输入格式…...
im6ull学习总结(二)Framebuffer 应用编程
1 LCD操作原理 linux中通过framebuffer驱动程序来控制LCD。framebuffer中包含LCD的参数,大小为LCD分辨率xbpp。framebuffer 是一块内存 内存中保存了一帧图像。 关于图像的帧指的是在图像处理中,一帧(Frame)是指图像序列中的单个…...
数据仓库 基本信息
数据仓库基本理论 数据仓库(英语:Data Warehouse,简称数仓、DW),是一个用于存储、分析、报告的数据系统。数据仓库的目的是构建面向分析的集成化数据环境,为企业提供决策支持(Decision Support)…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
