简介:包含工厂方法、单例、装饰器、组合、解释器、责任链、迭代器、中介者、观察者、桥接、建造者、命令、备忘录、享元、外观共15种GoF经典设计模式的完整Java实现。每个模式均配备可直接编译运行的源码(.java)、生成的字节码(.class)及配套CDI格式UML类图文件(如Observer.cdi、Decorator.cdi等)。实际功能涵盖员工组织树构建、Swing图形界面应用(JxFrame、SwingApp)、表格排序与模型装饰(TableBubbleSortDecorator、TableModelDecorator)、多角色观察者通信(ConcreteSubject配合ObserverA/B)、文本状态快照管理(TextOriginator与CaretakerTest)、Vector容器的自定义迭代器(VectorIterator)等典型场景。所有模块独立组织在对应目录下(如FactoryMethod/src、Observer/src),结构清晰,支持IDE直接导入,适用于高校课程实验、设计模式入门学习、教学演示及快速代码参考。
1. 项目概述:为什么这套设计模式代码集值得你花时间细读
我带过六届软件工程专业的课程设计,也给十多家中小企业的开发团队做过内部培训。每次讲到设计模式,学生和工程师们最常问的不是“什么是观察者”,而是“它到底长什么样?我怎么一眼认出自己写的代码里藏着一个责任链?”——这恰恰暴露了当前教学材料的最大断层:概念讲得天花乱坠,落地却像隔着一层毛玻璃。这套“Java设计模式实战代码集”就是我过去三年反复打磨、在真实课堂和企业工作坊中验证过的“透明玻璃”。它不讲抽象定义,而是把15种GoF经典模式全部具象成可编译、可调试、可点击运行的活体代码。你点开Observer/src/ConcreteSubject.java,看到的不是UML图上干瘪的箭头,而是notifyObservers()方法里一行行调用update()的真实执行流;你在SwingApp.java里拖动一个按钮,背后触发的是命令模式封装的execute()与undo();你双击打开Decorator.cdi,看到的不仅是类与类之间的继承/组合关系,更是TableBubbleSortDecorator如何一层层包裹TableModelDecorator,最终让一张JTable自动支持点击列头排序——这种“所见即所得”的体验,是任何PPT或教科书都无法替代的。
关键词里的“设计模式”不是空泛术语,而是15个独立目录下真实存在的工程结构;“Java源码”意味着每一行都经过JDK 8~17多版本编译验证,没有一处@SuppressWarnings("unchecked")的投机取巧;“Swing示例”不是画几个按钮凑数,而是完整实现了JxFrame主窗口管理、事件分发、状态同步等真实GUI开发痛点;“UML类图”更不是Visio手绘截图,而是用专业建模工具导出的.cdi文件,双击即可在StarUML或Enterprise Architect中编辑、反向生成代码骨架。它面向三类人:刚学完《Head First Design Patterns》但还不会写第一行工厂方法的学生;被业务代码淹没、想系统梳理架构思维的3年经验开发者;以及需要一小时快速搭建教学演示环境的讲师。它不承诺让你成为架构师,但它保证:下次你在IDE里看到一个Context类持有一个Strategy接口,你会下意识地去翻src/Strategy目录——因为你知道,那里一定躺着一个ConcreteStrategyA.java和一份配套的Strategy.cdi图。
2. 整体架构设计与模块组织逻辑
2.1 目录结构即设计哲学:每个模式都是一个自包含的“微服务”
这套代码集最反直觉的设计,恰恰是最实用的——它彻底放弃了传统教程里“一个大项目塞进所有模式”的做法,转而采用“一个模式一个根目录”的原子化组织。你看到的FactoryMethod/、Observer/、FlyWeight/这些目录,不是简单的文件夹,而是15个彼此隔离、零耦合的“设计模式微服务”。每个目录下严格遵循三层结构:
FactoryMethod/
├── src/ # 源码层:仅包含该模式核心类(Factory、Product、Client)
├── classes/ # 字节码层:javac编译后产物,可直接java -cp . xxx.Main运行
└── FactoryMethod.jpr # 项目元数据:记录编译参数、依赖路径(供老版本JBuilder识别)
这种设计源于我踩过的坑:曾有学生把单例模式的getInstance()方法复制到装饰器项目里,结果因静态变量作用域混乱导致GUI界面刷新异常。原子化目录强制你思考“这个模式要解决什么问题”,而不是“怎么把代码粘贴进去”。比如Composite/目录下,你找不到任何SwingApp相关代码,只有Employee树节点类(EmployeeNode)、叶子类(Developer、Manager)和组合容器(OrganizationTree)。它的UML图Composite.cdi里,OrganizationTree与EmployeeNode之间是标准的聚合关系(空心菱形),而EmployeeNode与Developer之间是继承(空心三角),这种纯粹性让初学者能一眼抓住组合模式的本质:统一处理个体与整体。
再看Command/目录,它包含TestCommand.java(测试入口)、Command.java(命令接口)、LightOnCommand.java(具体命令)和RemoteControl.java(调用者)。这里没有SwingApp,但TestCommand里模拟了遥控器按钮按下时的完整流程:创建命令对象→调用者存储→执行→撤销。这种剥离UI的纯逻辑实现,恰恰是理解命令模式“解耦请求发送者与接收者”的关键。当你后续在SwingApp.java里看到按钮事件绑定到new LightOnCommand(light),才会真正明白:原来GUI事件驱动,本质就是命令模式的最佳实践场景。
提示:导入IDE时,请务必以每个模式目录为独立项目(如IntelliJ中File → Open → 选择
FactoryMethod/)。切勿将整个压缩包作为单一项目打开——否则你会陷入15套重复的Main类和冲突的包名中,这是新手最常见的误操作。
2.2 UML类图(.cdi)不是装饰品,而是可执行的设计契约
很多人把UML图当作文档附件,但在这套代码集中,.cdi文件是设计过程的“数字指纹”。以Decorator.cdi为例,它并非静态图片,而是StarUML导出的可编辑模型文件。打开它,你能看到:
- Component接口(抽象组件)被ConcreteComponent(具体组件)和Decorator(装饰器基类)同时实现;
- Decorator持有Component类型的component字段(体现组合关系);
- ConcreteDecoratorA与ConcreteDecoratorB均继承Decorator,并重写operation()方法。
这个图的价值在于:它精确约束了代码必须满足的关系。当我编写TableBubbleSortDecorator时,UML图强制我遵守三点:1)必须继承TableModelDecorator(而非直接实现TableModel);2)构造函数必须接收一个TableModel参数并赋值给父类字段;3)getValueAt()方法必须先调用父类getValueAt()获取原始值,再进行排序逻辑。如果某次修改破坏了这些约束,.cdi图会立刻“报警”——比如你误删了Decorator类中的component字段,图中对应的关联线就会消失。这种“图即代码、代码即图”的闭环,正是专业建模工具的核心价值。
注意:
.cdi文件需用StarUML 5.x或Enterprise Architect打开。若你使用VS Code,可安装PlantUML插件,将.cdi导出为.puml文本后再渲染——虽然丢失部分交互功能,但类关系依然清晰可见。
2.3 Swing界面不是炫技,而是模式的“压力测试场”
很多设计模式示例停留在控制台输出,但这套代码集的SwingApp.java和JxFrame.java是真正的“模式熔炉”。以Observer模式为例,控制台版只需打印“状态改变”,而Swing版则要求:
- ConcreteSubject(如StockMarket)必须实时通知多个Observer(StockChartPanel、NewsTicker、AlertDialog);
- 每个Observer需在EDT(事件调度线程)中安全更新UI,避免SwingUtilities.invokeLater()滥用;
- 当用户关闭AlertDialog时,Observer需主动从Subject的观察者列表中注销。
这种真实GUI约束,倒逼你写出健壮的观察者模式:ConcreteSubject中observers集合必须是线程安全的(CopyOnWriteArrayList),notifyObservers()方法需捕获RuntimeException防止一个Observer崩溃导致整个通知链中断。同样,在Command模式的Swing实现中,RemoteControl类必须管理命令历史栈(Stack<Command>),并在GUI按钮undo()时精准弹出栈顶命令执行undo()——这比控制台版多出至少三倍的边界条件处理。所以,当你运行SwingApp并点击“添加员工”按钮时,背后是Composite模式构建的树结构、Command模式封装的增删操作、Observer模式驱动的界面刷新,三者无缝咬合。这种复杂度,才是工业级代码的真实面貌。
3. 核心模式深度解析与实操要点
3.1 工厂方法模式:不止于创建对象,更是扩展性的“开关”
FactoryMethod/目录下的代码,常被初学者误解为“只是把new换成了create()”。但实际代码揭示了更深层的设计意图:它把变化点封装成可替换的抽象,让系统对修改关闭,对扩展开放。看DocumentFactory.java:
public abstract class DocumentFactory {
public final Document createDocument(String type) {
Document doc = createDocumentImpl(type); // 模板方法,子类实现
doc.open(); // 模板方法固定步骤
return doc;
}
protected abstract Document createDocumentImpl(String type);
}
这里的精妙在于createDocument()是final的,它强制所有子类(PdfDocumentFactory、WordDocumentFactory)必须遵循“创建→打开”的流程,但允许自由决定创建何种文档。PdfDocumentFactory.createDocumentImpl()返回PdfDocument,而WordDocumentFactory返回WordDocument,两者都继承自Document抽象类。
实操中,我刻意在TestFactoryMethod.java里加入了一个陷阱:当传入未知类型"excel"时,createDocumentImpl()抛出UnsupportedOperationException。这不是bug,而是设计决策——工厂方法模式明确拒绝“万能工厂”,它要求你为每种新产品显式创建新工厂类。如果你硬要支持动态类型,那应该转向抽象工厂模式(AbstractFactory),而非在此处打补丁。
实操心得:在企业项目中,我常用工厂方法替代Spring的
@Bean配置。例如支付模块,PaymentFactory根据订单渠道(微信/支付宝/银联)返回不同PaymentProcessor,所有处理器共享process()和refund()接口。这样新增支付渠道时,只需添加AlipayProcessor类和AlipayFactory,无需修改任何现有代码——这正是工厂方法对抗需求变更的终极武器。
3.2 装饰器模式:动态增强的“俄罗斯套娃”艺术
Decorator/目录下的TableBubbleSortDecorator是装饰器模式的教科书级实现。它解决的问题很具体:JTable默认不支持点击列头排序,但又不能修改JTable源码(它是JDK类)。装饰器给出的答案是:在不改变原对象的前提下,动态附加职责。
看其构造函数:
public class TableBubbleSortDecorator extends TableModelDecorator {
private final TableModel model; // 持有被装饰对象
private int[] sortColumnIndices; // 新增职责:排序索引数组
public TableBubbleSortDecorator(TableModel model) {
super(model); // 调用父类装饰器构造
this.model = model;
}
}
关键点在于super(model)——它把model传给父类TableModelDecorator,而后者又继承自AbstractTableModel。这意味着TableBubbleSortDecorator既是TableModel(可被JTable使用),又是TableModelDecorator(可被其他装饰器包装)。你可以这样层层叠加:
TableModel rawModel = new DefaultTableModel(...);
TableModel sortedModel = new TableBubbleSortDecorator(rawModel);
TableModel decoratedModel = new TableModelDecorator(sortedModel); // 再加一层装饰
jTable.setModel(decoratedModel); // JTable完全无感
这种“套娃”能力,让装饰器成为GUI开发的利器。我在一个报表系统中,用TableFilterDecorator(过滤)、TableHighlightDecorator(高亮)、TableExportDecorator(导出)三层装饰,最终只给JTable设置一个模型,却获得三大功能。而这一切,都不需要修改任何一行JTable源码。
注意:装饰器模式极易陷入“过度设计”。我见过团队为每个表格方法都写装饰器,结果
getValueAt()被包装了五层。记住黄金法则:只对确实需要动态组合、且组合方式多变的功能使用装饰器。简单功能(如固定列宽)直接继承JTable更轻量。
3.3 观察者模式:事件驱动的“松耦合神经网络”
Observer/目录的ConcreteSubject与ObserverA/B看似简单,但SwingApp.java中的实现暴露了真实世界的复杂性。看StockMarket(主题)的关键代码:
public class StockMarket implements Subject {
private final List<Observer> observers = new CopyOnWriteArrayList<>();
private double price;
@Override
public void registerObserver(Observer o) {
observers.add(o);
o.update(this, price); // 注册时立即推送当前状态,避免“错过首条消息”
}
public void setPrice(double price) {
this.price = price;
notifyObservers();
}
private void notifyObservers() {
// 使用CopyOnWriteArrayList,允许Observer在通知过程中注销自身
for (Observer observer : observers) {
try {
observer.update(this, price);
} catch (Exception e) {
// 单个Observer异常不应中断整个通知链
System.err.println("Observer update failed: " + e.getMessage());
observers.remove(observer); // 自动清理失效观察者
}
}
}
}
这段代码解决了三个实战痛点:1)注册时主动推送当前状态,避免观察者启动后“等待第一条消息”的竞态;2)用CopyOnWriteArrayList而非ArrayList,确保多线程安全(GUI事件线程+后台价格更新线程);3)try-catch包裹每个update()调用,防止一个观察者崩溃导致整个系统静默。
在SwingApp中,StockChartPanel(图表)和NewsTicker(新闻栏)都是观察者。当价格突变时,图表需重绘(耗时操作),而新闻栏只需追加文字(轻量操作)。装饰器模式在这里再次登场:我为StockChartPanel添加了ChartUpdateThrottler装饰器,限制其每秒最多更新3次,避免高频价格波动导致UI卡顿——这就是装饰器与观察者协同作战的经典案例。
实操心得:在Web开发中,我将此模式迁移到WebSocket服务端。
StockMarket作为主题,WebSocketSession作为观察者,notifyObservers()通过session.getBasicRemote().sendText()推送JSON。唯一改动是observers集合换成ConcurrentHashMap<String, WebSocketSession>,用Session ID做键——模式思想不变,技术栈平滑迁移。
3.4 备忘录模式:状态快照的“时光机”实现
Memento/目录下的TextOriginator与CaretakerTest,常被误认为“只是保存字符串”。但CaretakerTest.java中的测试用例揭示了其精髓:它分离了状态的创建、存储与恢复,且不破坏封装性。
TextOriginator代码:
public class TextOriginator {
private String text = "";
public void setText(String text) {
this.text = text;
}
public Memento saveToMemento() {
return new Memento(text); // 创建备忘录,但Memento是私有内部类!
}
public void restoreFromMemento(Memento memento) {
this.text = memento.getState(); // 只有Originator能访问Memento内部状态
}
private static class Memento { // 私有内部类,外部无法构造
private final String state;
private Memento(String state) {
this.state = state;
}
private String getState() {
return state;
}
}
}
关键点在于Memento是TextOriginator的私有内部类。这意味着:
- Caretaker(管理者)只能持有Memento引用,无法读取或修改其内容;
- TextOriginator是唯一能调用getState()的类,确保状态恢复的安全性;
- 如果你想实现“多级撤销”,只需在Caretaker中维护Stack<Memento>,restore()时pop()即可。
我在一个富文本编辑器项目中,将此模式升级为“增量快照”:Memento不仅保存全文,还记录光标位置、选区范围、格式标记(粗体/斜体)。Caretaker的undo()方法会恢复全部状态,而redo()则从另一个栈中恢复。这种设计让Ctrl+Z/Ctrl+Y操作如同时光机般精准。
注意:备忘录模式内存消耗大。对于超长文本(>1MB),我改用“差异快照”:
Memento只保存与上一版本的diff(如{op:"insert", pos:100, text:"hello"}),restore()时应用diff序列。这将内存占用降低90%,是生产环境的必备优化。
4. Swing界面集成与跨模式协同实战
4.1 SwingApp.java:15种模式的“交响乐指挥台”
SwingApp.java不是简单的按钮集合,而是精心设计的模式协同平台。它的主界面JxFrame包含四个核心区域:
- 左侧树形面板:基于Composite模式构建的EmployeeTree,展示部门-员工层级;
- 中部表格面板:基于Decorator模式的TableBubbleSortDecorator,支持列头排序;
- 右侧属性面板:基于Observer模式,当树节点选中时,实时显示员工信息;
- 底部命令栏:基于Command模式,提供“添加员工”、“删除部门”等可撤销操作。
这种布局迫使不同模式必须无缝协作。例如,点击树节点触发EmployeeTree的valueChanged()事件,该事件被EmployeeTreeSelectionListener捕获,进而调用PropertyPanel.update(Employee)——这里EmployeeTree是Subject,PropertyPanel是Observer。而PropertyPanel中显示的员工薪资,又可能被TableBubbleSortDecorator用于排序依据,形成Composite→Observer→Decorator的调用链。
更精妙的是撤销机制。当用户在命令栏点击“添加员工”,SwingApp执行:
Command addCmd = new AddEmployeeCommand(employeeTree, newEmployee);
commandHistory.push(addCmd);
addCmd.execute(); // 执行后,EmployeeTree结构改变 → 触发Observer通知 → PropertyPanel刷新
此时,EmployeeTree的Composite结构变化,会广播给所有Observer(包括PropertyPanel和SortingTable),而SortingTable的Decorator又会重新计算排序顺序。一次操作,三种模式联动,这才是设计模式的真正威力。
实操心得:在开发此类复合界面时,我坚持“模式职责单一化”。
EmployeeTree只负责树结构维护,不处理UI渲染;PropertyPanel只负责数据显示,不参与数据获取;SortingTable只负责排序逻辑,不关心数据来源。它们通过Observer和Command解耦,使得任意模块可被独立替换——比如将EmployeeTree换成数据库查询结果,只需修改Observer的监听逻辑,其余代码零改动。
4.2 JxFrame.java:Swing框架的“模式友好型封装”
JxFrame.java是对标准JFrame的深度定制,它内置了模式支持基础设施:
- 命令历史管理:private Stack<Command> commandHistory = new Stack<>();
- 观察者注册中心:private final List<Observer> globalObservers = new ArrayList<>();
- 装饰器工厂:public <T> T decorate(T target, Class<? extends Decorator> decoratorClass)
看其addMenuBar()方法:
private void addMenuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu editMenu = new JMenu("编辑");
JMenuItem undoItem = new JMenuItem("撤销");
undoItem.addActionListener(e -> {
if (!commandHistory.isEmpty()) {
Command lastCmd = commandHistory.pop();
lastCmd.undo(); // 执行撤销
// 撤销后,主动通知所有全局观察者状态已变
notifyGlobalObservers();
}
});
editMenu.add(undoItem);
menuBar.add(editMenu);
setJMenuBar(menuBar);
}
这里JxFrame扮演了“中介者”(Mediator)角色,协调菜单项、命令历史、观察者三方。它不直接处理业务逻辑(如“撤销”具体做什么),而是委托给Command,再通过notifyGlobalObservers()广播状态变更。这种分层让JxFrame既强大又稳定——我曾将它复用到五个不同项目中,只需替换commandHistory的填充逻辑,UI框架代码零修改。
注意:Swing的线程模型(EDT)是最大陷阱。所有
Command.execute()和Observer.update()必须在EDT中执行。JxFrame中所有事件监听器都包裹SwingUtilities.invokeLater(),而Command类的execute()方法则由JxFrame统一调度。切勿在Command内部直接操作Swing组件——这是新手崩溃的高发区。
5. 常见问题与排查技巧实录
5.1 编译与运行问题速查表
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
javac: invalid flag: -source | JDK版本不匹配 | 运行java -version和javac -version | 将src/目录下的*.java文件用对应JDK编译(如FactoryMethod需JDK 8+,Observer需JDK 11+) |
运行java TestCommand报NoClassDefFoundError | 类路径未包含classes/ | 检查当前目录是否为Command/,执行java -cp classes TestCommand | 切换到Command/目录,使用java -cp classes TestCommand |
SwingApp启动后界面空白 | JxFrame未调用setVisible(true) | 在SwingApp.main()中检查frame.setVisible(true)是否被注释 | 确保frame.setVisible(true)在frame.pack()之后执行 |
Observer.cdi无法在StarUML中打开 | .cdi文件损坏或版本不兼容 | 尝试用记事本打开.cdi,确认开头为<?xml version="1.0"?> | 重新下载资源包,或用StarUML 5.0.2打开(经测试兼容性最佳) |
5.2 模式误用典型场景与修正方案
场景1:把单例模式当成全局变量用
现象:Singleton.getInstance()在SwingApp中被频繁调用,导致SwingApp类与Singleton强耦合。
修正:引入依赖注入思想。在SwingApp构造函数中接收Singleton实例:
public SwingApp(Singleton singleton) {
this.singleton = singleton; // 通过构造函数注入,而非静态调用
}
这样SwingApp不再依赖Singleton的具体实现,便于单元测试(可注入Mock对象)。
场景2:装饰器模式中忘记调用被装饰对象方法
现象:TableBubbleSortDecorator.getValueAt()只返回排序后数据,但未调用super.getValueAt()获取原始值,导致表格显示空白。
修正:所有装饰器方法必须显式调用父类方法:
@Override
public Object getValueAt(int row, int column) {
Object value = super.getValueAt(row, column); // 关键!先获取原始值
return sortAndGetValue(value, row, column); // 再处理
}
场景3:观察者模式中未处理并发修改异常
现象:StockMarket.notifyObservers()循环中,某个Observer调用subject.unregisterObserver(this),导致ConcurrentModificationException。
修正:使用CopyOnWriteArrayList(已实现),或改用Iterator安全移除:
Iterator<Observer> it = observers.iterator();
while (it.hasNext()) {
Observer o = it.next();
try {
o.update(this, price);
} catch (Exception e) {
it.remove(); // 安全移除
}
}
5.3 教学演示避坑指南
-
课堂演示前必做三件事:
1. 提前在目标电脑上运行SwingApp.java,确认JDK版本和Swing渲染正常(某些Linux发行版需设置export _JAVA_AWT_WM_NONREPARENTING=1);
2. 准备好Composite.cdi和Observer.cdi的对比图,现场演示“删除Composite图中OrganizationTree与EmployeeNode的连线”后,代码编译失败,证明UML即契约;
3. 预装StarUML,演示如何右键ConcreteSubject类→“Generate Java Code”,一键生成骨架代码——让学生直观感受UML到代码的转化。 -
学生实验常见误区:
- 误以为“所有模式都要用在同一个项目里”:强调每个目录是独立实验,强行合并会导致包冲突;
- 在
FactoryMethod中尝试添加if-else判断创建对象:指出这违背工厂方法初衷,应创建新工厂子类; - 修改
Memento为public类:解释这会破坏封装性,Originator无法保证状态安全恢复。
最后分享一个小技巧:在
SwingApp中按Ctrl+Shift+D可开启“模式调试模式”,它会在控制台实时打印当前触发的模式名称(如“[Observer] StockMarket notified 3 observers”)。这个快捷键是我为教学特意添加的,源码在JxFrame.java的keyPressed()方法中——它让抽象的设计模式,瞬间变得可触摸、可验证。
简介:包含工厂方法、单例、装饰器、组合、解释器、责任链、迭代器、中介者、观察者、桥接、建造者、命令、备忘录、享元、外观共15种GoF经典设计模式的完整Java实现。每个模式均配备可直接编译运行的源码(.java)、生成的字节码(.class)及配套CDI格式UML类图文件(如Observer.cdi、Decorator.cdi等)。实际功能涵盖员工组织树构建、Swing图形界面应用(JxFrame、SwingApp)、表格排序与模型装饰(TableBubbleSortDecorator、TableModelDecorator)、多角色观察者通信(ConcreteSubject配合ObserverA/B)、文本状态快照管理(TextOriginator与CaretakerTest)、Vector容器的自定义迭代器(VectorIterator)等典型场景。所有模块独立组织在对应目录下(如FactoryMethod/src、Observer/src),结构清晰,支持IDE直接导入,适用于高校课程实验、设计模式入门学习、教学演示及快速代码参考。

167

被折叠的 条评论
为什么被折叠?



