🌈 引言:设计模式就像乐高积木
- 适配器:让不同形状的积木完美拼接
- 装饰器:给积木添加炫酷灯光效果
- 代理:遥控积木完成复杂动作
- 组合:将小积木搭建成宏伟城堡
结构型模式
主要用于描述对象之间的关系,包括类和对象的组合、接口和继承等方面。这些模式可以帮助我们更好地组织和管理代码,提高代码的可重用性和可维护性。
🧩 七大结构性模式速览
模式名称 | 核心思想 | 生活类比 |
---|---|---|
适配器模式 | 转换接口,让不兼容的类协同工作 | 电源转换器 |
桥接模式 | 分离抽象与实现,独立变化 | 遥控器和电视的分离 |
组合模式 | 树形结构管理部分-整体关系 | 公司组织架构 |
装饰器模式 | 动态添加功能,不修改原有代码 | 给手机加装保护壳 |
外观模式 | 简化复杂子系统接口 | 酒店前台一站式服务 |
享元模式 | 共享细粒度对象,节省资源 | 共享单车 |
代理模式 | 控制对象访问,增强功能 | 明星经纪人 |
适配器模式 Adapter Pattern-让不兼容的接口协同工作
将一个类的接口转化成客户希望的另一个接口,使得原本由于接口不兼容而无法一起工作的类可以一起工作。
适配器模式包括以下几个组成部分:
- 目标接口(Target Interface):客户端期望的接口,类似于目标插座。
- 适配器(Adapter):充当两个不兼容接口之间的桥梁,使得它们可以互相通信,类似于转换插头。
- 适配者(Adaptee):需要被适配的对象,它的接口与目标接口不兼容,类似于英标插头。
- 客户端(Client):使用目标接口的对象。
应用场景
- 当需要将一个已有的类或接口与另一个不兼容的类或接口进行协同工作时。
- 当需要对一个已有的类或接口进行修改,以满足客户端的需求时,但是不希望修改该类或接口的源代码。
- 当需要重新使用一个已有的类或接口,但是不能直接使用该类或接口的方法时。
代码实战:让旧设备支持新接口
// 旧设备(Adaptee)
class OldPrinter {void printDocument(String doc) {System.out.println("Old Printer: " + doc);}
}// 新接口(Target)
interface NewPrinter {void print(String content);
}// 适配器(Adapter)
class PrinterAdapter implements NewPrinter {private OldPrinter oldPrinter;public PrinterAdapter(OldPrinter oldPrinter) {this.oldPrinter = oldPrinter;}@Overridepublic void print(String content) {oldPrinter.printDocument(content);}
}// 客户端
public class Client {public static void main(String[] args) {OldPrinter oldPrinter = new OldPrinter();NewPrinter printer = new PrinterAdapter(oldPrinter);printer.print("Hello, Adapter Pattern!");}
}
输出:
Old Printer: Hello, Adapter Pattern!
使用小结
适配器模式是一种非常有用的设计模式,在JDK中被广泛应用,可以提供一致的接口,比如:
1.Java IO 流是一个常见的适配器模式的例子。它提供了一组标准的接口来访问各种类型的数据源,包括文件、网络连接、内存等等。每个数据源都有自己的接口,但是 Java IO 流可以将这些不同的接口转换为标准的接口,从而提供一致的访问方式。
2.Java Servlet API 也是一个常见的适配器模式的例子。它定义了一组接口来处理 HTTP 请求和响应,包括 doGet()、doPost()、doPut() 等等。每个 Servlet 都必须实现这些接口,但是用户只需要实现其中的一部分即可。这些 Servlet 之间的适配工作由 Servlet 容器完成。
桥接模式
将抽象部分与实现部分分离,以便它们可以独立地变化
**使用场景:**当你希望在不同的维度上变化(如图形的形状和颜色)时。
类比: 就像遥控器和电视之间的桥接。
代码示例:
// 实现接口
interface Color {void applyColor();
}// 抽象类
abstract class Shape {protected Color color;public Shape(Color color) { this.color = color; }abstract void draw();
}// 具体实现
class Red implements Color {public void applyColor() { System.out.print("红色的"); }
}class Circle extends Shape {public Circle(Color c) { super(c); }void draw() {color.applyColor();System.out.println("圆形");}
}
组合模式
将对象组合成树形结构以表示 部分-整体 的层次结构,使得客户端使用单个对象或者组合对象具有一致性。
使用场景:当你希望以统一的方式处理单个对象和组合对象时。
类比: 公司组织架构(部门包含子部门)
interface Component {void show();
}class Leaf implements Component {private String name;public Leaf(String name) { this.name = name; }public void show() { System.out.println("叶子:" + name); }
}class Composite implements Component {private List<Component> children = new ArrayList<>();public void add(Component c) { children.add(c); }public void show() {for(Component c : children) {c.show();}}
}
装饰器模式-动态添加功能
在不修改现有对象的情况下,动态地给一个对象添加一些额外的职责(通过将一个对象包装在另一个对象中来扩展它的行为),就增加功能而言,装饰器模式比生成子类方法更加灵活。
应用场景
1.当需要在不修改现有对象结构的前提下增加新的功能或特性时,可以使用装饰器模式。这样可以保持原有代码的稳定性和兼容性,同时也可以增加代码的灵活性和可扩展性。
2.当需要动态地向对象添加或删除功能时,可以使用装饰器模式。这样可以在运行时动态地添加或删除功能,而不需要修改现有的代码。
3.当需要为多个对象添加相同的功能时,可以使用装饰器模式。这样可以将相同的功能封装在装饰器中,以便于复用和管理。
代码实现
该示例代码中,Shape 是一个接口,定义了一个 draw 方法,表示绘制图形的操作。Circle 是一个实现 Shape 接口的类,表示一个圆形。
ShapeDecorator 是一个装饰器抽象类,实现了 Shape 接口,并包含一个 Shape 类型的变量 decoratedShape,表示要装饰的对象。RedShapeDecorator 是一个具体的装饰器类,继承了 ShapeDecorator 类,并实现了 draw 方法,在绘制图形时添加了一个红色的边框。
在 main 方法中,我们创建了原始对象 Circle,以及两个装饰器对象 RedShapeDecorator,分别装饰了 Circle 和 Rectangle 对象。通过调用 draw 方法,我们可以看到对象被动态地添加了一个红色的边框,而不需要修改原有的代码。
// 定义接口
interface Shape {void draw();
}// 实现接口
class Circle implements Shape {@Overridepublic void draw() {System.out.println("Shape: Circle");}
}class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("Shape: Rectangle");}
}// 装饰器抽象类
abstract class ShapeDecorator implements Shape {protected Shape decoratedShape;public ShapeDecorator(Shape decoratedShape){this.decoratedShape = decoratedShape;}public void draw(){decoratedShape.draw();}
}// 具体装饰器类
class RedShapeDecorator extends ShapeDecorator {public RedShapeDecorator(Shape decoratedShape) {super(decoratedShape);}@Overridepublic void draw() {decoratedShape.draw();setRedBorder(decoratedShape);}private void setRedBorder(Shape decoratedShape){System.out.println("Border Color: Red");}
}// 测试代码
public class DecoratorPatternDemo {public static void main(String[] args) {// 创建原始对象Shape circle = new Circle();// 创建装饰器对象Shape redCircle = new RedShapeDecorator(new Circle());Shape redRectangle = new RedShapeDecorator(new Rectangle());// 调用方法System.out.println("Circle with normal border");circle.draw();System.out.println("\nCircle of red border");redCircle.draw();System.out.println("\nRectangle of red border");redRectangle.draw();}
}
使用小结
在实际应用中,装饰器模式经常用于图形界面(GUI)开发、输入/输出流处理、缓存机制、日志记录等领域,可以有效地提高程序的可扩展性和可维护性。比如
1.装饰器模式被广泛应用于Java IO流中,以提供各种不同的功能,如缓存、压缩、加密等等。例如,可以使用 BufferedReader 来缓存读取文件的数据,使用 GZIPOutputStream 来压缩数据,使用 CipherOutputStream 来加密数据等等。
2.Java Swing 组件是一个经典的装饰器模式的例子。它允许在运行时动态地向组件添加功能,如边框、背景、文本等等。例如,可以使用 BorderFactory 来向组件添加边框,使用 Color 来设置组件的背景颜色,使用 Font 来设置组件的字体等等。
3.在 Spring 框架中,装饰器模式被广泛应用于实现 AOP。AOP通过代理模式和装饰器模式实现。JDK 动态代理和 CGLIB 动态代理两种方式实现代理模式,使用装饰器模式对目标对象进行包装,从而实现通知 (Advice) 的织入。例如,可以使用 @Transactional 来添加事务处理的功能,使用 @Cacheable 来添加缓存处理的功能,等等。
外观模式
为子系统中的一组接口提供一个一致的接口,使得子系统更容易使用
- 特点:提供统一入口,简化复杂系统
- 场景:一键操作(如电脑开机)、API网关
- 类比:餐厅服务员(隐藏厨房复杂流程)
class CPU { void start() {} }
class Memory { void load() {} }class ComputerFacade {private CPU cpu = new CPU();private Memory memory = new Memory();public void start() {cpu.start();memory.load();System.out.println("电脑启动完成");}
}
享元模式
运用共享技术来有效地支持大量细粒度对象的复用
- 特点:共享细粒度对象,节省内存
- 场景:文字编辑器字符对象、棋子游戏
- 类比:棋盘共享棋子样式(颜色/形状)
class TreeType {private String name;private String color;// 构造函数...
}class TreeFactory {static Map<String, TreeType> cache = new HashMap<>();static TreeType getTreeType(String name, String color) {String key = name + color;if(!cache.containsKey(key)) {cache.put(key, new TreeType(name, color));}return cache.get(key);}
}
代理模式:控制对象访问
解决的问题
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个真实对象的访问,起到对代理对象已有功能的增强。
分类
➢静态代理(静态定义代理类,就是在程序运行前就已经存在代理类的字节码文件)
➢动态代理(动态生成代理类,在程序运行期间,由JVM根据反射等机制动态生成,在运行前并不存在代理类的字节码文件)
1. 静态代理:专属经纪人模式
角色分工:
- Subject:抽象主体角色(明星接口):定义核心业务(唱歌、演戏)
- RealSubject:真实主体角色(真实明星):周杰伦(专注艺术创作)
- ProxySubject:代理主体角色(经纪人代理):处理商务对接、安保等杂务
代码实战:明星经纪人系统
// 明星接口
interface Star {void sing(String song);void act(String movie);
}// 真实明星
class JayChou implements Star {public void sing(String song) {System.out.println("周杰伦演唱: " + song);}public void act(String movie) {System.out.println("周杰伦出演: " + movie);}
}// 经纪人代理(静态)
class StarAgent implements Star {private Star target; // 被代理的明星public StarAgent(Star target) {this.target = target;}public void sing(String song) {System.out.println("【经纪人】对接演唱会场地");target.sing(song);System.out.println("【经纪人】处理粉丝互动");}public void act(String movie) {System.out.println("【经纪人】审核剧本");target.act(movie);System.out.println("【经纪人】安排媒体采访");}
}// 客户端调用
public class Client {public static void main(String[] args) {Star jay = new JayChou();Star agent = new StarAgent(jay);agent.sing("七里香"); // 经纪人全权代理}
}
输出
【经纪人】对接演唱会场地
周杰伦演唱: 七里香
【经纪人】处理粉丝互动
静态代理特点
- ⚙️ 手动编写代理类,且个明星需专属经纪人。当需要代理多个明星,需要编写多个代理类
- 🛡️ 代理逻辑硬编码(商务流程固定)
- ➕ 代理类与真实主体类耦合度太高,接口增加方法时,需修改所有代理类
相比与于静态代理,动态代理则不存在上述的诸多问题,下面我们进入 JDK 的动态代理。
2.动态代理:万能代购模式
DK原生动态代理的组成分为三个部分: 抽象主题角色 、 真实主题角色 、 增强主题角色 。
- 抽象主题角色和代理模式的抽象主题角色是一样的,都是抽象出来的接口或类,对于JDK原生动态代理而言,抽象主题角色就是接口。
- 真实主题角色和代理模式的真实主题角色一样,都是被代理类。
- 增强主题角色在JDK原生代理中值的是实现了 InvocationHandler 接口的类,其目的是对真实主题角色的方法的增强。 InvocationHandler 接口中只有一个方法 invoke 方法,所有的动态代理对象中的映射方法在执行时都是调用的 InvocationHandler 接口中的 invoke 方法,在调用 invoke 方法时,动态代理对象会将 被代理对象的方法 和 动态代理对象映射的方法的参数 传递给 InvocationHandler 的 invoke 方法, invoke 方法的实现是由程序员编写的,这样程序员就可以在 被代理对象的方法 执行 前后 进行增强。
🔧 核心功能拆解
组件 | 作用 | 生活比喻 |
---|---|---|
接口(Interface) | 定义核心功能规范 | 明星的工作合同 |
真实对象(Target) | 实现接口的核心业务类 | 明星本人 |
InvocationHandler | 代理逻辑处理器(处理额外操作) | 经纪人的工作手册 |
Proxy.newProxyInstance | 动态生成代理对象 | 经纪公司生成经纪人 |
-- 定义抽象主题接口
public interface Star {void act(); // 拍戏void sing(); // 唱歌
}-- 定义真实主体角色类
public class RealStar implements Star {public void act() {System.out.println("周董在拍戏...");}public void sing() {System.out.println("周董在唱《青花瓷》...");}
}-- 经纪人,动态代理
public class StarProxy implements InvocationHandler {private Object target; // 被代理的明星public Object createProxy(Object target) {this.target = target;// 动态生成代理对象return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("【经纪人】签合同");System.out.println("【经纪人】安排化妆");Object result = method.invoke(target, args); // 调用真实对象的方法System.out.println("【经纪人】收尾款");return result;}
}// 客户端动态生成代理
public class Client {public static void main(String[] args) {Star realStar = new RealStar();Star proxy = (Star) new StarProxy().createProxy(realStar);proxy.act(); // 输出:// 【经纪人】签合同// 【经纪人】安排化妆// 周董在拍戏...// 【经纪人】收尾款proxy.sing();// 输出同上流程}
}
动态代理特点
- 🌀 运行时动态生成代理类(无需提前编写)
- 🎯 可代理任意接口类型
- 🔄 新增接口方法自动适配
📊 静态代理 vs 动态代理 核心对比
模式名称 | 静态代理 | 动态代理 |
---|---|---|
实现方式 | 手动编写代理类 | 运行时动态生成代理类 |
灵活性 | 一个代理类对应一个接口 | 一个处理器可处理多个接口 |
代码冗杂度 | 接口方法变化需修改代理类 | 接口扩展无需修改代理逻辑 |
典型应用 | 简单业务场景 | Spring AOP、RPC框架等 |
性能开销 | 无额外运行时开销 | 反射调用带来微小性能损耗 |
实现复杂度 | 简单直观 | 需理解反射机制和InvocationHandler |
🔧 技术原理拆解
静态代理实现要点
- 代理类与真实类实现相同接口
- 代理类持有真实对象的引用
- 在调用前后添加增强逻辑
动态代理底层机制
sequenceDiagramClient->>Proxy: 调用代理方法Proxy->>InvocationHandler: 转发调用InvocationHandler->>RealObject: 反射调用真实方法RealObject-->>InvocationHandler: 返回结果InvocationHandler-->>Proxy: 返回增强结果Proxy-->>Client: 最终响应
- Proxy类:动态生成代理类的工厂
- InvocationHandler:所有代理方法的统一入口
- 反射机制:实现方法调用的动态转发
🛠 实战场景选择指南
- 选静态代理:当代理逻辑简单、接口稳定时(如日志记录)
- 选动态代理:当需要代理多个不同接口、或代理逻辑需要统一管理时(如事务管理)
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
掌握代理模式的核心,就像从雇佣专属管家升级为拥有AI智能助手!静态代理是精通的开始,动态代理才是企业级开发的常态。理解这一模式,将为学习Spring AOP、MyBatis插件等框架打下坚实基础! 🚀
💡 高频面试三连
装饰器模式与继承的区别?
- 装饰器:运行时动态扩展,更灵活
- 继承:编译时静态扩展,耦合度高
代理模式的应用场景?
- 远程代理(RMI)
- 虚拟代理(延迟加载)
- 保护代理(权限控制)
适配器模式的两种实现方式?
- 类适配器:通过继承实现
- 对象适配器:通过组合实现