结构型模式 · 设计模式

Adaptor-适配器

代码块JAVA · 33 行收起展开
// 目标接口
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

接口不匹配, 但是却想复用旧代码

主要角色

  1. 继承适配者 ( 核心 )

    • 通过继承, 类适配器拿到了适配者的功能实现
  2. 实现目标接口

    • 适配器类声明实现了目标接口, 所以可以被当初目标接口类型使用
  3. 重写接口方法

    • 方法内调用继承自适配者的方法完成实际功能
    • 就是套壳: 内部执行的是适配者的方法

缺点

  • 违背了合成复用原则(继承了target), 适用于客户端有一个规范接口的情况
  • 如果目标是类&&适配者是类, 不能实现多继承

JDK

Pasted image 20260415211847

桥接模式

why

当一个类有多个变化维度,并且这些维度可以独立扩展时, 用桥接模式将抽象和实现分离,避免类爆炸

比如我们写 👉 设备:手机、电脑
👉 品牌:苹果、小米、华为

两两组合类爆炸, 但是如果只写 抽象类 Device +接口 Brand

代码块JAVA · 15 行收起展开
// 一个维度:设备
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

组合模式:

代码块JAVA · 17 行收起展开
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() + 自己增强逻辑
  • 作用: 功能加强
代码块JAVA · 74 行收起展开
// 抽象构件
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源码

代码块JAVA · 15 行收起展开
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 自己的内部类。

代码块JAVA · 21 行收起展开
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-享元

抽象享元定义接口 + 具体享元保存共享状态 + 外部状态动态注入 + 工厂管理共享对象

代码块JAVA · 14 行收起展开
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

火车站买票, 太麻烦, 直接火车站在多个地方设代售点 Pasted image 20260416164827

JDK动态代理

  1. 有接口的情况下, 基于反射实现 这个动态代理Proxy类, 提供了一个静态方法newProxyInstance来获取代理对象
代码块JAVA · 56 行收起展开
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; ---|
}
  1. JDK动态代理本质上生成的是一个实现了你给定接口的类 这个代理类既是给定接口的实现类, 也返回一个实现指定接口的实现类对象

代理类将我们提供的匿名内部类形参传递给了父类, 用InterceptorHandler接口接收的

Pasted image 20260416174058

CGLib

代码块JAVA · 28 行收起展开
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动态生成.classASM框架在内存中动态生成字节码后加载

动态代理 & 静态代理

对比项静态代理动态代理
代理类的生成时机编译期手写或预生成运行时由 JVM 或字节码库自动生成
实现方式程序员写一个类实现相同接口,再手动调用目标对象使用 Proxy(JDK)或 CGLIB(生成子类)动态生成
维护成本高,每个接口都要写代理类低,一个通用代理即可代理任意对象
扩展性弱,修改接口或逻辑要改所有代理类强,可通过反射或字节码增强统一修改
底层机制源代码 + 编译期固定结构反射 + 运行时字节码生成
是否依赖接口是(代理类必须实现相同接口)
在代理类实现目标接口, 并内置一个该接口的实现类
取决于类型:→ JDK 必须有接口→ CGLIB 不需要接口
性能较快(无反射)稍慢(JDK 用反射;CGLIB 生成字节码)
常用场景简单业务逻辑封装框架层通用拦截
(如 Spring AOP)

代理模式的优缺点:

  • 优点

    1. 拓展目标对象的功能
    2. 中介保护目标对象
    3. 降低客户端和目标对象的耦合度
  • 缺点: 增加系统复杂度

使用场景

  1. 远程代理 让本地对象像访问本地方法一样去访问远程对象 (例如RMI、RPC) -> 隐藏网络通信细节

  2. 虚拟代理 当对象创建代价高时 (如加载大图片、视频、数据库连接) 时, 用代理延迟加载 -> 延迟加载

  3. 保护代理 对不同用户or调用方, 控制对真实对象的访问权限 -> 权限控制

  4. 智能引用代理 在访问真实对象时, 附加额外操作 (统计访问次数、添加缓存、事务控制) -> 增强访问行为

防火墙代理:

当你将浏览器配置成使用代理功能时, 防火墙就将你的浏览器的请求转给互联网; 当互联网返回响应时, 代理服务器再把它转给你的浏览器.