常见设计模式详解

发布于:2025-09-08 ⋅ 阅读:(12) ⋅ 点赞:(0)

单例模式

单例模式是创建型设计模式的核心模式之一。主要目标是:确保一个类在程序生命周期中仅存在一个实例,并提供唯一的访问点来获取改实例

核心特性

  • 唯一实例:类的构造方法必须被私有化,禁止外部通过New创建实例。从根源避免多实例。
  • 提供唯一访问点。

单例模式 创建实例时,需要注意创建时机、并发控制和性能、防止通过特殊手段创建多实例。

常见实现方式

1、饿汉模式:在类加载时,创建对象(使用JVM加载机制,加载静态变量)线程安全保证单例。

public class Test {
// 1.私有构造方式
	private Test() {}
// 2。静态变量初始化对象
	public static final Test TEST= new Test();
//3.提供唯一访问点
	public static  Test getTest() {
		return TEST;
	}
}

2、枚举单例:利用Java枚举单例特性,在类加载时创建且单例。
枚举类:构造方法被隐式私有化,禁止外部创建。同时天然支持序列化/反序化安全。

public enum Test{
	TEST;
	// 枚举类可以添加自定义方法
	public void doSomething() {
		。。。
	}
}
使用时 Test.TEST

3、懒汉式:(线程安全版本)需要使用锁保证线程安全
在获取实例的方法上添加锁,限制并发。

public class Test {
	private Test() {}
	// 声明唯一实例
	private static Test test;
	// 包装创建实例对象方法
	public static synchrionized Test getTest() {
		 if (test == null) {
		 	test = new Test();
	 	}
	 	return test;
	}
}	

4、双重锁检查机制:在 懒加载 基础上,优化锁粒度。仅在实例化初始化代码块加锁,而非整个方法。同时通过两次检查 实例对象是否为空确保线程安全。
注意:需要在变量上使用Volatile修饰,禁止指令重排,否则对线程下可能获取到”半初始化的对象“

public class Test {
	private Test() {}
// 声明唯一实例	
	private statis Volatile Test test;
// 提供唯一访问点
	public static Test getTest() {
		// 检查实例是否初始化
		if (test == null) {
			// 加锁创建对象
			synchronized(Test.class) {
			// 二次检查,防止多线程等待锁时,重复创建实例
			if (test==null) {
				test = new Test();
				// 不加volatile时,JVM可能重排序为:1.分配内存 → 2.返回实例 → 3.初始化实例
                    // 导致其他线程获取到“未初始化完成”的实例
			}
		}
	}
	return test;
}

5、静态内部类:相较于饿汉模式(使用静态变量),同样JVM加载 外部类时,静态内部类不会加载,只有调用时才会加载并初始化静态变量。天然的懒加载和线程安全

public class test {
	private Test() {}
	// 定义静态方法,初始化单例对象
	private static Test SingleTest() {
	// 静态内部类中初始化,静态实例
		private static final Test TEST = new Test();
	|
	// 提供唯一访问点
	public static Test getTest() {
		return SingleTest.TEST;
	}
}

注意事项

1、反射破坏与防护(枚举单例 天然防护反射)
通过Java反射机制,可强制调用私有化构造方法,创建新实例。

//反射破坏饿汉式单例
public class Reflact{
	public static void main(String[] args) {
		// 获取单例类的构造方法
		Constructor<Test> testConstructor = Test.class.getDeclareConstructor();
		//取消访问检查(强制访问私有方法)
		testConstructor.setAccessible(true);
		// 通过反射创建实例,*(此时有2个对象)
		Test test1 = Test.getTest();// 获取单例对象
		Test test2 = testConstructor.newInstance(); // 反射获取对象
		System.out.printf(test1==test2); // 输出false
	}
}

防护措施:在私有构造方法中添加”实例已存在“的判断,若已存在则抛出异常,阻止反射创建新实例。

public class Test{
	private static final Test TEST = new Test();
	private Test() {
		// 反射防护,若实例已存在,则报错
		if (TEST!=null) {
			throw new IllegalStateException("单例类禁止通过反射创建实例")
		}
	}
	public static Test getTest() {
		return TEST;
	}
}

2、序列化/反序列 破坏与防护
破坏原理:
若单例类实现了Serializable接口,遇到序列化将实例写入文件,在反序列化读取时,会创建一个新实例(破坏单例)。

// 破坏单例模式
public class SerializableTest {
	public static void main(String[] args) {
		// 序列化单实例到文件。
		Test test1 = Test.getTest(); // 获取单例对象
		// 序列化到文件
		ObjectOutPutStream oos = new ObjectOutPutStream(new FileOUtPutStrean("singleton.txt"));
		oos.writeObject(test1);
		// 反序列化
		ObjectInPutStream ois = new ObjectInPutStream(new FileInPutStream("singleton.txt"));
		Test test02 = (Test)ois.readObject();
		System.out.printf(test1 == test02); 输出false
	}
}

防护措施:
在单例类中 添加readResolve()方法,反序列化时会优先调用该方法 返回已拥有的实例,而非创建新实例:

public class Test Implements Seralizable {
	private Test() {}	
	private static volatile Test test;
	public static Test getTest() {
		if(test==null) {
			synchronized(Test.class) {
				if(test==null) {
					test = new Test();
				}
			}
		}
	return test
	} 
	public Object readResolve() {
		return getTest();
	}

总结

  • 不要过度使用单例:不是所有工具类都单例,对于无状态的(如StringUtils)无需单例,直接使用静态方法即可。
  • 注意线程安全:
  • 不要滥用全局访问;单例的“全局访问”可能导致代码 耦合。后续难以重构。

适用场景

资源复用和状态统一

  • 资源密集型对象:数据库连接池、线程池、Redis连接池等,避免频繁创建/销毁。
  • 全局状态管理:应用配置类(如application.properties的配置),全局日志器(logger)、用户会话管理器(多模块共享状态)
  • 工具类/服务类:Spring中的Bean(默认单例),分布式Id生成器,全局计数器等。确保逻辑一致。

场景匹配

无参数、无需懒加载 → 枚举单例(最简单安全);
无参数、需懒加载 → 静态内部类(比 DCL 简洁);
需动态参数、需懒加载 → 双重检查锁(DCL)(需加 volatile);
单线程 / 低频场景 → 饿汉式 / 同步方法懒汉式(不推荐生产使用)。

工厂模式

是创建型设计模式的核心代表,核心思想:将对象的创建与使用分离。
通过 工厂类,负责对象实例化,解决直接new对象导致的代码耦合,扩展性差的问题。
分为:简单工厂模式、工厂方法模式、抽象工厂模式。

为了解决:通常使用直接new 对象,调用方法。
存在弊端:**耦合高,扩展性差、职责不单一。**即创建对象和使用对象绑定,使用对象的类即负责创建由负责使用。

工厂模式引入工厂角色,将对象的创建逻辑集中管理。

常见实现方式

1、简单工厂模式
“入门版”通过单一工厂类根据输入参数动态决定创建哪种实例
适合;产品种类少,变化不频繁的场景。解决直接new的场景。

角色:

  • 产品接口:定义产品统一行为。
  • 具体产品:实现产品具体类。
  • 简单工厂:核心,提供静态方法根据输入创建对应的具体产品实例。
// 产品接口
public imterface Fruit {
	void eat();// 统一行为,吃水果
}
// 具体产品1;苹果
public class Apple Impaement Fruit {
	@Override
	public void eat() {
		。。。
	}
}
// 具体产品2
public void Banana implements Furit {
	@Override
	public void eat() {...}
}

// 简单工厂类 核心
public class FuritFactory{
	//静态方法,根据参数创建产品
	public static Fruit createFruit(String type){
	switch(type.toLowCase()){
		case "apple":
			return new Apply();
		case "banana":
			return new Banana();
		default:
			throw new IllegalAgumentException("未知水果类型")}
	}
}
使用
Fruit apple = Fruit。createFruit("apple"); apple.eat();

解耦、集中管理,便于维护。
违反开闭原则,产品过多时工厂臃肿。

2、工厂方法模式:
针对简单工厂“违反开闭原则”问题,工厂方法模式:将单一工厂拆分""多个工厂+多个工厂实现类。每个工厂负责单一产品。

角色:

  • 产品接口:统一所有产品的行为。
  • 具体产品:实现产品的具体类。
  • 工厂定义:定义共产的统一行为。提供产品的抽象方法。
  • 具体工厂:实现工厂对应的具体产品。

优缺点:符合开闭原则,单一职责。类数量冗余,复杂度提升。

3、抽象工厂模式
当产品是“产品簇”一组相关链的产品,而非单个产品。工厂方法模式无法高效管理,此时抽象工厂可以很好解决,它能创建一整套 产品族。

角色:

  • 抽象产品接口:按照产品等级定义多个产品(如水果、果汁)。
  • 具体产品:针对多个抽象产品定义对应实现(如:苹果、苹果汁)。
  • 抽象工厂接口:定义创建 一个产品族所有方法(如创建水果,创建果汁)。
  • 具体工厂实现:实现抽象工厂的每个抽象方法。

小结:简单工厂:工厂直接创建所有产品。工厂方法:将工厂专一化,一个工厂产一个产品。抽象工厂:定义不同的产品,相关的产品生产交给一个抽象工厂生产(即结合简单工厂和工厂方法。一个工厂生产只一类产品)。

适用场景:

简单场景:产品固定(如仅 3-5 种)、几乎不扩展 → 选简单工厂(代码少、易维护)。
单产品等级 + 频繁扩展:如仅需创建 “水果”,但水果种类不断加 → 选工厂方法。
多产品等级 + 产品族关联:如需要同时创建 “水果 + 果汁”“按钮 + 文本框” → 选抽象工厂。

应用案例:
JDK中的工厂模式:Collection 的iterator():每个集合都有自己的迭代器工厂。
SPring中的Bean Factory(抽象工厂)负责创建Spring容器中的Bean,ApplicationContext是具体实现。
Mybatis的SqlSessionFactory抽象工厂。创建SqlSession产品族核心对象。DefaultSQL SessionFactory是具体实现。

观察者模式

迭代模式

装饰器模式

结构型设计模式的一种,核心是动态的给对象添加额外的功能,同时不改变对象本身结构。
该模式 比继承更灵活,能够在不创建大量子类的情况下,实现功能复用和组合。

解决问题:不使用 装饰器模式时,为对象添加新功能,只能通过 修改源码或者继承子类
特点
通过组合而非继承的 方式,解决了上述问题。

  • 可以动态、灵活地给对象添加功能。
  • 可以叠加多个功能
  • 新增功能无序修改原有代码。

角色

  • 组件接口(Component):定义被装饰 对象装饰器共同行为。所有对象的顶层抽象。
  • 具体组件(ConcreteComponent): 被装饰的原始对象,实现组件接口。
  • 装饰器抽象类(Decorator):实现组件接口,同时持有组件接口的引用(通过构造函数传入),是所有具体装饰器的父类。
  • 具体装饰器(ConcreteDecorator):继承装饰器抽象类,负责给组件添加具体功能,

代码示例:

// 抽象组件接口
interface Component{
	public void operation(); // 声明组件基础功能
}
// 具体组件角色
class ConcreteComponent implements Component{
	public ConcreteComponent() {
		System.out.println("创建具体组件角色");
	}
	public void operation () {
		System.out.println("组件角色功能");
	}
}

// 抽象装饰角色
class Decorator implements Component {
	private Component component; // 装饰器中提供 原始组件的调用
	
	public Decorator(Component component) { // 通过构造器传入
		this.component = component;
	}
	// 装饰器中调用 原始组件的功能
	public void operation() {
		component.operation();
	}
}
// 具体装饰器对象。
class ConcreteDecorator extends Decorator{
	// 初始化 装饰器对象
	public ConcreteDecorator(Component component) {
		super(component);	
	}
	
	public void operation() {
		super.operation();
		addBehavior(); // 新增的装饰方法
	}
	public void addBehavior() {
		System.out.println("为具体组件新增额外功能);
	}
}

// 使用时:
public static void main(String[] args) {
	Component component = new ConcreteComponent(); //原始 组件对象
	component.operation();
	System.out.println("------");
	Component decorator = new ConcreteDecorator(component);
	decorator.operation();
}

优缺点

  • 比继承更灵活:可以给对象添加功能,且可以灵活组合多个功能。
  • 符合开闭原则:新增功能只需要添加新的装饰类,无序修改原有代码。
  • 单一职责:每个装饰类只负责一个功能。
  • 可重复使用:装饰器可以在不同场景中被 重复使用。
  • 多层装饰后 调试困难:多个装饰器装饰后,排查问题需要逐层分析装饰器逻辑。
  • 必须基于接口编程:装饰器和被装饰对象必须实现同一个接口,否则无法组合。
  • 客户端需要了解装饰器的顺序,有时装饰器的顺序 会影响结果。

应用案例

  • java IO流:使用装饰器模式:
    • InputStream是组件接口
    • FileInputStream:是具体组件
    • FilterInput是装饰器抽象类
    • BufferInputStream(缓冲功能)、DataInputStream(数据转换功能)是具体装饰器对象。
  • Spring框架
    • TransacationAwateDataSourceProxy:给数据源添加事务管理功能。
    • HttpServletRequestWrapper:对HTTP请求进行包装扩展。

与装饰器相关模式的区别

  • 与继承:
    • 继承是静态的,编译时就确定功能,
    • 装饰器是动态的,运行时可以灵活的添加和组合功能。
  • 与适配器模式的区别:
    • 装饰器模式:不改变接口,只扩展功能
    • 适配器模式:改变接口,使不兼容的接口可以一起工作。
  • 与代理模式:
    • 装饰器模式:主要目的是添加功能,通常透明的扩展对象。
    • 代理模式:主要目的是 控制访问,通常包含对对象的某种约束。

原型模式

代理模式

结构型设计模式的一种,核心思想通过一个代理对象, 间接访问 ”目标对象“,在不修改目标对象代码的前提下,为其提供额外功能(如增强、控制访问、隐藏细节等)。模式在生活中随即可见。

针对场景:

直接访问对象时,可能会遇到以下问题:

  • 目标对象创建成本高(如数据库连接,大文件加载),需要延迟初始化(懒加载)。
  • 目标对象在远程服务器,直接访问需要处理网络通信细节(如RPC调用)。
  • 需要给目标对象的方法添加额外逻辑(如日志记录、权限校验、事务管理),但不能修改目标对象代码。
  • 需要限制对目标对象的访问(如某些方法只允许管理员调用)。

代理模式:通过引入 "代理对象”作为 中间层,完美解决了这些问题,实现了功能增强和“核心业务的耦合。

核心角色

  • 目标接口(Subject):定义目标对象和代理对象的共同行为,是两者的统一接口。
  • 目标对象(Real Subject):真正执行业务逻辑的对象,实现目标接口。
  • 代理对象(Proxy):实现目标接口,持有目标对象的引用,在调用目标方法前后可以添加额外逻辑。

实现方式

  • 动态代理:
    代理类在运行时期动态生成,无需手动编写代理类代码,Java中常用的动态代理有两种方式
    • 基于JDK动态代理:基于接口实现,只能代理接口
    • 基于CGLIB动态代理:基于继承实现,可以代理类。字节码生成
      JDK动态代理示例
// 实现Invocationhandler接口,定义增强逻辑
import java.Lang.reflect.invocationHandler;
import java.Lang.reflect.Method;

public class LogInvocationHandler implements invocationHandler {
	// 目标对象
	private Object target;
	public LogInvocationHandler(Object target) {
		this.target = target;
	}
	//当调用代理对象的方法时,会自动代用该方法。
	@Overried
	public Object invoke(Object proxy,Method method,Object[] args) throws Throw able {
		// 方法调用前增强
        System.out.println("【日志】准备执行方法:" + method.getName());
        // 调用目标对象的方法
        Object result = method.invoke(target, args);
        // 方法调用后增强
        System.out.println("【日志】方法" + method.getName() + "执行完成");
        return result;
	}
}
  • 静态代理:
    代理类在编译期就已确定,需要手动编写代理类代码。
    代码示例
// 抽象接口 
public interface UserService {
	void login(String username,String passward); // 定义目标方法
	void logout();
}

// 目标对象
public class UserServiceImple implements UserService {
	@Overried
	public void loging(String username,String password) {
		System.out.println("用户“【"+username+"]登陆成功");
	}
	@Overried
	public void logout(String username,String password) {
		System.out.println("退出")
	}
}

// 静态代理,通过硬编码 指定代理逻辑
public class UserServiceProxy implements UserService {
	// 持有目标对象的引用
	private UserService userService;
	// 通过构造函数传入目标对象。
	public UserServiceProxy(UserService userService) {
		this.userService = userService;
	}
	// 实现抽象接口的方法,调用目标对象的方法 进行代理。
	@Overried
	public void login(String username,String password) {
		// 前置增强
		System.out.println("[日志]准备登陆操作");
		// 目标对象方法
		user Servicelogin(username,password);
		//后置方法
		System.out.println("[日志]登陆完成");
	} 
	
	@Override
	public void logout() {
		System.out.println("前置增强");
		userService.logout();
		System.out.println("后置增强");
	}
}

代理的类型

  • 远程代理(Remote Proxy):
    • 为远程对象(另一台服务器上的对象) 创建本地代理,隐藏网络通信细节。
    • 如RPC框架中,本地代理对象负责 与远程服务通信。
  • 虚拟代理(Virtual Proxy):
    • 用于创建开销大的对象,实现懒加载(只有在需要时才创建目标对象)。
    • 例如:图片加载框架中,先用占位符代替真实图片,当用户滚动到该图片时在加载。
  • 保护代理(Protection Proxy):
    • 控制对 目标对象的访问,实现权限控制。
    • 例如:某些方法只允许管理员访问,代理对象会先检查访问者用户权限。
  • 日志记录代理(Logging Proxy):
    • 在方法调用前后记录日志 用于调试或审计。
    • 例如:SpringAOP中日志切面就是 基于代理模式实现的。
  • 缓存代理:
    • 缓存方法调用结果,避免重复计算或请求。
    • 例如:Mybatis中的二级缓存,Redis缓存代理。

优缺点

优点

  • 解耦:代理对象与目标对象分离,增强逻辑(如日志、权限)与核心逻辑分离。
  • 扩展性好:可以在不修改目标对象的情况下,通过代理对象零或添加功能。
  • 保护目标对象:隐藏对目标对象的实现细节,控制访问权限。
  • 灵活性高:动态代理可以在运行时为不同目标对象生成代理。

缺点

  • 增加系统复杂性:引入代理层,增加了代码理解和调试难度。
  • 性能损耗:代理对象会额外处理增强逻辑,可能代理轻微的性能开销。
  • 静态代理的局限性:需要为每个目标对象编写代理类,代码冗余;接口变更时,代理类也需要更改。

与装饰器模式

代理模式:控制访问、隐藏细节、增强功能。通常指导目标对象的类型。通常增强逻辑时固定的(日志、权限)。客户端通常无法察觉不到代理对象。
装饰器模式:动态添加功能,不需要知道目标对象的类型。增强逻辑可以灵活组合,客户端知道并主动组合装饰器。

策略模式

行为型设计模式的一种,核心解决同一种行为在不同场景下有多种实现方式,且需要灵活切换的场景。

针对问题

  • 避免大量If-else或switch判断逻辑(如不同支付方式,不同排序算法)。
  • 解决”硬编码“问题,当需要新增实现方式时,无需修改原先代码,只需扩展新策略。
  • 实现行为与使用行为的代码分离,使策略可以独立 进行完善。

角色

  • 策略接口(Strategy):定义所有支持的 算法/行为的公共接口(如paymentStrategy定义的pay()方法)。
  • 具体策略(ConcreteStrategy):实现策略接口,包含具体的算法逻辑。
  • 上下文(Context):持有策略接口的引用,负责调用具体的策略。

代码示例

// 订单支付系统为例,不同方式支付作为策略
// 策略接口
public interface PaymentStrategy{
	void pay(double amount);
}
// 具体实现 支付宝
public class AlipayStrategy implements PayStrategy {
	@Override
	public void pay(double amount) {
		System.out.println("使用支付宝支付");
	}
}
// 具体实现 微信
public class WechatpayStrategy implements PaymentStrategy {
	@Override
	public void pay(double amount) {
		System.out.println("使用weixin支付");
	}
}

// 上下文;订单
public class Order {
	private PaymentStratrgy strategy;

	public Order(PaymentStrategy strategy) {
		this.strategy = strategy;
	}
	 // 调用策略执行支付
    public void pay(double amount) {
        strategy.pay(amount);
    }
}
// 5. 客户端使用
public class Client {
    public static void main(String[] args) {
        // 选择支付宝策略
        Order order1 = new Order(new AlipayStrategy());
        order1.pay(100); // 输出:使用支付宝支付:100.0元

        // 切换为微信策略
        Order order2 = new Order(new WechatPayStrategy());
        order2.pay(200); // 输出:使用微信支付:200.0元
    }
}

优点

  • 消除大量条件判断:用策略选择代替if-else,代码更清晰,易维护。
  • 符合开闭原则:新增只需要实现接口,无需修改上下文或其他策略。
  • 策略可复用:同一策略可在不同场景中被多个上下文复用。
  • 灵活性高:运行时可动态切换策略。

缺点

  • 增加类数量:每个策略都需要对应一个类,策略过多时会导致类膨胀。
  • 客户端需了解策略:客户端必须所有策略的存在,才能选择合适的策略。
  • 策略间无法共享状态:若策略需要共享数据,需要额外设计(如上下传递)。

与相关模式区别:

  1. 与状态模式:
    • 策略模式:策略的选择由客户端决定,策略间无依赖。
    • 状态模式:状态的切换由上下文根据内部状态自动触发,状态间可能有依赖。
  2. 与模板方法模式:
    • 策略模式:通过组合实现算法替换,更灵活(运行时切换)。
    • 模板方法模式:通过继承实现算法骨架固定,步骤可变,更稳定(编译时确定)。

模板模式

建造者模式


网站公告

今日签到

点亮在社区的每一天
去签到