结构型模式 · 设计模式
Adaptor-适配器
代码块收起展开
// 目标接口
interface Target {
void request();
}
// 已有类(不兼容)
class Adaptee {
public void specificRequest() {
System.out.println("原始方法");
}
}
// 适配器(用组合)
class Adapter implements Target {
private Adaptee adaptee; //内置一个不兼容的适配者
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest(); // 翻译调用
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Target target = new Adapter(new Adaptee());
target.request();
}
}你想用 request() 方法, 但是却只有一个适配者Adaptee, 方法却是specificRequest()
所以写一个target接口的子实现类Adapter, 内置一个Adaptee, 然后重写request方法, 内部调用specificRequest()来执行, 就实现了旧人新衣
why
接口不匹配, 但是却想复用旧代码
主要角色
-
继承适配者 ( 核心 )
- 通过继承, 类适配器拿到了适配者的功能实现
-
实现目标接口
- 适配器类声明实现了目标接口, 所以可以被当初目标接口类型使用
-
重写接口方法
- 方法内调用继承自适配者的方法完成实际功能
- 就是套壳: 内部执行的是适配者的方法
缺点
- 违背了合成复用原则(继承了target), 适用于客户端有一个规范接口的情况
- 如果目标是类&&适配者是类, 不能实现多继承
JDK
桥接模式
why
当一个类有多个变化维度,并且这些维度可以独立扩展时, 用桥接模式将抽象和实现分离,避免类爆炸
比如我们写
👉 设备:手机、电脑
👉 品牌:苹果、小米、华为
两两组合类爆炸, 但是如果只写 抽象类 Device +接口 Brand
代码块收起展开
// 一个维度:设备
abstract class Device {
protected Brand brand;
}
// 另一个维度:品牌
interface Brand {
void run();
}
class Phone extends Device {}
class Computer extends Device {}
class Apple implements Brand {}
class Xiaomi implements Brand {}主要角色
- 抽象类 & 实现接口
- 具体抽象子类 & 具体实现
好处 & 场景
- 两个变化维度中任意扩展一个维度, 不需要修改原有系统
- 如有wmv视频文件类型, 只需再定义一个类实现VideoFile接口
| 模式 | 维度关系 | 关注点 | 类图形态 |
|---|---|---|---|
| 桥接模式 | 多维度正交变化 | 解耦抽象与实现 | 横向交叉 |
| 装饰者模式 | 单维度功能增强 | 动态叠加行为 | 纵向套娃 |
Composite-组合
why
文件系统中文件和文件夹都有 “打开/删除/展示”
如果不用组合模式, 会写出if else if
组合模式:
代码块收起展开
abstract class Node {
abstract void operate();
}
class File extends Node {
void operate() {}
}
class Folder extends Node {
List<Node> children;
void operate() {
for (Node n : children) {
n.operate();
}
}
}主要角色
- 抽象根节点 (Component) : 定义各层次的共有方法和属性
- 树枝节点 (Composite) : 存储子节点, 组合树枝节点和叶子节点形成树形结构
- 叶子节点 (Leaf) : 叶子节点对象
总结
让抽象组件有叶子节点和容器节点的并集属性 , 然后让他们两个实现抽象组件, 并且只重写需要的方法
一些问题
此处可能违背了接口隔离, 员工继承了不需要的add类, 解决方法是调用后抛出异常, 然后用多态来实现不区分节点还是容器的操作
核心思想
让单个对象 (叶子) 和组合对象 (容器) 在结构上具有一致性
- 通过在抽象组件中同时定义“叶子行为”和“组合行为”
- 容器节点重写组合相关方法 (如addO、removeO)
- 叶子节点只实现自身行为 (不需要的可抛异常)
优点
- 让客户端忽略层次的差异, 方便控制整体
- 简化了客户端代码
两种模式
-
透明组合 抽象根节点中有所有管理成员对象的方法: 增删改查等 透明但安全
-
安全组合 抽象根节点中不生命任何用于管理成员对象的方法, 而是在树枝节点Menu类中声明并实现这些方法
不透明但安全
Decorator-装饰者
why
不改变原有对象结构的情况下, 为其增加一些额外功能
角色
| 角色 | 作用 |
|---|---|
| Component(抽象构件) | 定义核心对象的接口 |
| ConcreteComponent(具体构件) | 核心对象,实现接口的核心功能 |
| Decorator(装饰抽象类) | 内置 Component 对象,实现 Component 接口,同时提供额外功能接口 |
| ConcreteDecorator(具体装饰类) | 扩展装饰抽象类,实现具体功能增强 |
抽象装饰器
- 继承/实现抽象组件 (保证接口统一) , 确保能用到内部组件的方法
- 必须有一个构造方法, 参数是抽象组件
- 方法实现: 调用内部组件的方法
具体装饰器
- 构造方法同样接受抽象组件, 并传给super (抽象装饰器构造器)
- 每个方法调用super.method() + 自己增强逻辑
- 作用: 功能加强
代码块收起展开
// 抽象构件
interface Coffee {
String getDescription();
double cost();
}
// 具体构件(原始对象)
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "普通咖啡";
}
@Override
public double cost() {
return 10.0;
}
}
// 装饰器抽象类
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
// 具体装饰器:加糖
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + " + 糖";
}
@Override
public double cost() {
return coffee.cost() + 2.0;
}
}
// 具体装饰器:加牛奶
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + " + 牛奶";
}
@Override
public double cost() {
return coffee.cost() + 3.0;
}
}
// 测试
public class Client {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
coffee = new SugarDecorator(coffee); // 加糖
coffee = new MilkDecorator(coffee); // 再加牛奶
System.out.println(coffee.getDescription());
System.out.println(coffee.cost());
}
}JDK源码
代码块收起展开
public class FilterInputStream extends InputStream{ //实现
protected InputStream in;//聚合
protected FilterInputStream(InputStream in){
super();
this.in=in;
}
public int read() throws IOException{
return in.read();
}
public int read(byte[] b) throws IOExcetion{
return in.read(b);
}
}静态代理 & 装饰者模式
-
相同点
- 都要实现与目标类相同的业务接口
- 都能不修改目标类的前提下增强目标方法
-
不同点
-
目的不同, 装饰者是为了增强目标对象, 静态代理是为了保护和隐藏目标对象
-
获取目标对象构建的地方不同 (和$proxy0差不多) 装饰者是由外界传递进来, 可以通过构造方法传递 静态代理是在代理类内部创建以此来隐藏目标对象
-
Facade-外观
requestfacade的所有方法几乎执行人都是内部的request对象 也就是说它是一个“门面”, 外面看到的是ServletAPI, 里面实际干活的还是 Tomcat 自己的内部类。
代码块收起展开
public final class RequestFacade implements HttpServletRequest{
private Request request; //被封装的处理
public RequestFacade(Request request){
this.request = request
}
@Override
public String getParameter(String name){
return request.getParameter(name);
}
@Override
public String getHeader(String name){
return request.getHeader(name);
}
@Override
public HttpSession getSession(){
return request.getSession();
}
}why
解耦调用者与底层系统的复杂关系
-
安全隔离 防止开发者直接操作Tomcat内部对象, 破坏容器运行逻辑 比如
Request里有连接池、缓存等资源, 一旦外部调用释放就会爆炸 -
稳定的API Servlet 规范规定开发者只应该接触
HttpServletRequest接口 Tomcat 可以随意更换内部实现, 而不影响外部代码 -
解耦 外部模块(如Servlet) 和容器实现彻底解耦 外部只依赖
Servlet规范, 不依赖Tomcat具体实现
what
对复杂系统提供一个统一的对外接口, 让调用者不需要关心内部细节
优缺点
优点:
- 降低了子系统与客户端之间的耦合度, 使得子系统的变化不会影响调用的客户类
- 对客户屏蔽子系统组件, 减少客户处理的对象数目, 并使得子系统使用起来更加容易
缺点:
- 不符合开闭原则, 修改很麻烦
使用场景
- 对分层结构系统构建时, 使用外观模式
- 一个复杂系统的子系统很多时, 外观模式可以系统设计一个简单的接口供外界访问
- 当客户端与多个子系统之间存在很大的联系时, 外观模式
FlyWeight-享元
抽象享元定义接口 + 具体享元保存共享状态 + 外部状态动态注入 + 工厂管理共享对象
代码块收起展开
public final class Integer extends Number implements Comparable<Integer>{
private static class IntegerCache{
static final int low = -128;
statoc final int high = 127;
static final Integer cache[];
}
public static Integer valueOf(int i){
if(i>=IntegerCache.low&&i<=IntegerCache.high){
return IntegerCache.cache[ i + (-IntegerCache.low) ];
}
return new Integer(i);
}
}why
用共享的方式高效地支持大量细粒度对象, 当系统中存在大量相似对象时, 我们不希望为每个对象都重新分配内存, 所以我们把这些对象可以共享的部分抽取出来(内部状态), 而不共享的部分(外部状态) 则由外部在使用时动态传入
两种状态
- 内部(可共享): 对象固有的、不会变化的部分, 如颜色字体类型等
- 外部(不可共享): 对象使用时依赖的、变化的部分, 如位置、用户、时间
- 享元工厂: 负责维护共享对象池, 确保重复对象不被创建
优缺点
优点:
- 极大减少内存中 相似/相同 的对象数量
- 享元模式中的外部状态相互独立, 且不影响内部状态
缺点:
- 分离内部状态和外部状体, 使程序逻辑复杂
JDK
IntegerCache 就是享元工厂, Integer对象的值是内部状态(可共享) int i 参数是外部输入 (用于查找享元);
可以看到 Integer 默认先创建并缓存-128~127之间数的 Integer 对象,
当调 valueof 时如果参数在 -128~127 之间则计算下标并从缓存中返回,
否则创建一个新的Integer 对象。
Proxy-代理
why
- 代理对象作为访问对象和目标对象之间的中介
- 按代理类生成时机不同分为: 静态代理+动态代理, 静态是在编译器就生成, 动态是JAVA运行时动态生成, 动态代理又有JDK代理和CGLib代理
主要角色
-
抽象主题: 通过 接口 & 抽象类 声明真实主题和代理对象实现的业务方法
-
真实主题: 实现抽象主题的具体业务, 代理对象所代表的真实对象, 是最终要引用的对象
-
代理类: 提供了与真实主题相同的接口, 其内部含有对真实主题的引用, 它可以访问、控制or拓展真实主题的功能
eg
火车站买票, 太麻烦, 直接火车站在多个地方设代售点

JDK动态代理
- 有接口的情况下, 基于反射实现
这个动态代理Proxy类, 提供了一个静态方法
newProxyInstance来获取代理对象
代码块收起展开
Proxy.newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h
);
UserService proxy = (UserService) Proxy.newProxyInstance(
UserServiceImpl.class.getClassLoader(),
new Class[]{UserService.class},
(p, m, a)->{
"前置增强".sout
Object res = m.invoke(new UserServiceImpl(), a);
"后置增强".sout
return res;
}
);
/*主要流程:
1. 用目标接口接收 proxy.newProxyInstance(...) 返回的实现类 (也是代理类)
2. 内部:
a. 生成代理类字节码
-> (既继承proxy, 又实现目标接口, 并且将(...)的method重写)
b. 加载代理类 --> ( 用(...)的指定接口的类加载器加载 )
c. 调用代理类的构造器以创建代理类对象
$Proxy0(InvocationHandler h){
super(h); //父类Proxy的构造器把h存起来
}
1. 于是这个$Proxy就有了InvocationHandler, 并且能够执行(...)的重写的invoke方法
*/
public final class $Proxy0 extends Proxy implements SellTickets{
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler){
super(invocationHandler);
}
static {
m3 = Class.forName("com.itheima.pattern.proxy.jdk_proxy.SellTickets")
.getMethod("sell", new Class[0]);
}
public final void sell(){
this.h.invoke(this, m3, null);
} |----------------------------
} |
|
|
pubilc class Proxy{ |
protected InvocationHandler h; ---|
}- JDK动态代理本质上生成的是一个实现了你给定接口的类 这个代理类既是给定接口的实现类, 也返回一个实现指定接口的实现类对象
代理类将我们提供的匿名内部类形参传递给了父类, 用InterceptorHandler接口接收的
CGLib
代码块收起展开
Class MyInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {// 1. 代理类实现MethodInterceptor接口, 并重写intercept方法
"before method".sout;
Object res = proxy.invokeSuper(obj, args);
"after method".sout;
return res;
}
}
public class CglibDemo{
public static void main(String[] args){
Enhancer enh = new Enhancer();
// 2. 将目标类设置为Enhancer的父类
enhancer.setSuperclass(UserService.class);
// 3. 设置回调函数并提供代理对象
enhancer.setCallback(new MyInterceptor());
UserService proxy = (UserService) enhancer.create();
// 4. create获得proxy
proxy.addUser();
}
}
没有接口的情况下 ,基于继承实现动态代理, 因此其提供的代理对象是目标子类 (需要set)
代理类还要实现methodInterceptor接口, 重写interceptor方法
JDK & CGLib
JDK动态代理基于接口, CGLib基于继承
| 维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 核心机制 | 基于JAVA反射(Java.lang.reflect.Proxy) 和 InvocationHandler | 基于字节码增强(net.sf.cglib.proxy.Enhancer), ASM生成子类 |
| 生成对象方式 | 生成一个实现相同接口的类 | 生成一个继承目标类的子类 |
| 方法调用 | 调用接口方法时 ->转发到 InvocationHandler.invoke() | 调用子类方法时 ->转发到 MethodInterceptor.intercept() |
| 限制 | 目标类必须有接口 | 目标类不能是final, 被代理方法也不能是final (否则无法重写) |
| 底层类加载器 | ProxyClassFactory动态生成.class | ASM框架在内存中动态生成字节码后加载 |
动态代理 & 静态代理
| 对比项 | 静态代理 | 动态代理 |
|---|---|---|
| 代理类的生成时机 | 编译期手写或预生成 | 运行时由 JVM 或字节码库自动生成 |
| 实现方式 | 程序员写一个类实现相同接口,再手动调用目标对象 | 使用 Proxy(JDK)或 CGLIB(生成子类)动态生成 |
| 维护成本 | 高,每个接口都要写代理类 | 低,一个通用代理即可代理任意对象 |
| 扩展性 | 弱,修改接口或逻辑要改所有代理类 | 强,可通过反射或字节码增强统一修改 |
| 底层机制 | 源代码 + 编译期固定结构 | 反射 + 运行时字节码生成 |
| 是否依赖接口 | 是(代理类必须实现相同接口) 在代理类实现目标接口, 并内置一个该接口的实现类 | 取决于类型:→ JDK 必须有接口→ CGLIB 不需要接口 |
| 性能 | 较快(无反射) | 稍慢(JDK 用反射;CGLIB 生成字节码) |
| 常用场景 | 简单业务逻辑封装 | 框架层通用拦截 (如 Spring AOP) |
代理模式的优缺点:
-
优点
- 拓展目标对象的功能
- 中介保护目标对象
- 降低客户端和目标对象的耦合度
-
缺点: 增加系统复杂度
使用场景
-
远程代理 让本地对象像访问本地方法一样去访问远程对象 (例如RMI、RPC) -> 隐藏网络通信细节
-
虚拟代理 当对象创建代价高时 (如加载大图片、视频、数据库连接) 时, 用代理延迟加载 -> 延迟加载
-
保护代理 对不同用户or调用方, 控制对真实对象的访问权限 -> 权限控制
-
智能引用代理 在访问真实对象时, 附加额外操作 (统计访问次数、添加缓存、事务控制) -> 增强访问行为
防火墙代理:
当你将浏览器配置成使用代理功能时, 防火墙就将你的浏览器的请求转给互联网; 当互联网返回响应时, 代理服务器再把它转给你的浏览器.