前言
设计模式是软件开发中经过验证的解决方案,它们帮助我们解决常见的软件设计问题。本文将详细介绍三种最常用且重要的设计模式:单例模式(Singleton Pattern) 、工厂模式(Factory Pattern)和代理模式(Proxy Pattern)。
一、单例模式(Singleton Pattern)
1.1 模式概述
单例模式是创建型设计模式,其核心思想是确保一个类只有一个实例,并提供一个全局访问点。这种模式在需要控制资源访问、避免重复创建对象等场景中非常有用。
核心特点:
- 唯一性:一个类只能创建一个实例对象
- 全局访问:通过静态方法获取实例,整个应用程序共享该实例
- 延迟加载(可选):在需要时才创建实例,节省资源
应用场景:数据库连接池、日志系统、配置管理器、线程池等。
1.2 饿汉式实现
public class HungrySingleton {
// 类加载时就创建实例,线程安全
private static final HungrySingleton instance = new HungrySingleton();
// 私有构造函数,防止外部实例化
private HungrySingleton() {
}
// 提供全局访问点
public static HungrySingleton getInstance() {
return instance ;
}
public void showMessage() {
System.out.println("Hello from HungrySingleton!");
}
}
代码解释:
- 类加载时立即创建instance实例,使用final关键字确保引用不可变
- 私有构造函数,防止通过new关键字创建实例
- 提供静态方法getInstance获取唯一实例
优点:实现简单,线程安全
缺点:类加载时就创建实例,即使不使用也会占用资源,可能造成资源浪费
1.3 懒汉式实现
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
// 同步方法获取实例,保证线程安全
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
代码解释:
- synchronized关键字修饰后保证在多线程环境下只创建一个实例
- 加锁和if判断双重检查,只有实例为空时才创建
- 实现了延迟加载,节省资源
缺点:每次调用getInstance都需要加锁,性能开销较大
1.4 双重检查锁定实现
public class DoubleCheckedSingleton {
// volatile确保可见性和禁止指令重排序
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {
}
public static DoubleCheckedSingleton getInstance() {
// 第一次检查:避免不必要的同步
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
// 第二次检查:确保在多线程环境下只创建一个实例
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
代码解释:
- 用volatile修饰,确保多线程环境下instance变量的可见性,防止指令重排序
- 第一次进行if判断是为了减少不必要的加锁,提升性能
- 同步代码块内的第二次检查, 确保在多线程环境下只创建一个实例
优点:延迟加载、线程安全、性能优秀,是推荐的实现方式
1.5 静态内部类实现
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
}
// 静态内部类,只有在调用getInstance()时才会被加载
private static class SingletonHolder {
private static final StaticInnerClassSingleton instance =
new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return SingletonHolder.instance ;
}
}
代码解释:
- 创建一个静态内部类,只有在调用getInstance()时才会被加载
- 静态内部类的实例创建发生在被加载时,且由JVM保证线程安全
- 这种方式既实现了延迟加载,又保证了线程安全,是最佳实现方式
优点:延迟加载、线程安全、性能优秀,是推荐的实现方式
1.6 单例模式总结
| 实现方式 | 延迟加载 | 线程安全 | 性能 | 推荐程度 |
|---|---|---|---|---|
| 饿汉式 | 否 | 是 | 优 | 一般 |
| 懒汉式 | 是 | 是 | 差 | 不推荐 |
| 双重检查锁定 | 是 | 是 | 优 | 推荐 |
| 静态内部类 | 是 | 是 | 优 | 推荐 |
二、工厂模式(Factory Pattern)
2.1 模式概述
工厂模式是创建型设计模式,其核心思想是将对象的创建与使用分离,通过工厂类来创建对象。这种模式降低了客户端代码与具体产品类之间的耦合度,提高了系统的可维护性和可扩展性。
模式分类:
- 简单工厂(Simple Factory):一个工厂类根据参数创建不同的产品
- 工厂方法(Factory Method):定义一个创建对象的接口,由子类决定实例化哪个类
- 抽象工厂(Abstract Factory):提供一个创建一系列相关对象的接口
2.2 简单工厂模式
// 产品接口
public interface Product {
void operation();
}
// 具体产品A
public class ConcreteProductA implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductA operation");
}
}
// 具体产品B
public class ConcreteProductB implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductB operation");
}
}
// 简单工厂
public class SimpleFactory {
public Product createProduct(String type) {
if ("A".equals(type)) {
return new ConcreteProductA();
} else if ("B".equals(type)) {
return new ConcreteProductB();
} else {
throw new IllegalArgumentException("Unknown product type: " + type);
}
}
}
// 客户端使用
public class SimpleFactoryClient {
public static void main(String[] args) {
SimpleFactory factory = new SimpleFactory();
Product productA = factory.createProduct("A");
productA.operation();
}
}
代码解释:
- product接口:定义产品的通用行为,所有产品都实现这个接口
- simpleFactory是一个简单工厂类,根据参数创建相应的产品对象
- 客户端代码通过工厂类获取产品,无需知道具体的产品类名
优点:封装了对象创建过程,客户端代码与具体产品类解耦
缺点:违反开闭原则,增加新产品需要修改工厂类的d
2.3 工厂方法模式
// 产品接口
public interface Product {
void operation();
}
// 具体产品
public class ConcreteProductA implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductA operation");
}
}
public class ConcreteProductB implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductB operation");
}
}
// 抽象工厂接口
public interface Factory {
Product createProduct();
}
// 具体工厂A - 创建产品A
public class ConcreteFactoryA implements Factory {
@Override
public Product createProduct() {
return new ConcreteProductA();
}
}
// 具体工厂B - 创建产品B
public class ConcreteFactoryB implements Factory {
@Override
public Product createProduct() {
return new ConcreteProductB();
}
}
// 客户端使用
public class FactoryMethodClient {
public static void main(String[] args) {
Factory factoryA = new ConcreteFactoryA();
Product productA = factoryA.createProduct();
productA.operation();
}
}
代码解释:
- Factory接口用来定义创建产品的抽象方法
- ConcreteProductA和 ConcreteProductB都是具体工厂类,负责创建具体产品
- 每个工厂只负责创建一种产品,符合单一职责原则
- 客户端依赖抽象工厂接口和抽象产品接口,添加的新产品类只需要实现Factory接口即可
优点:符合开闭原则,新增产品只需添加新的工厂类
2.4 抽象工厂模式
// 产品族接口A
public interface ProductA {
void operationA();
}
// 产品族接口B
public interface ProductB {
void operationB();
}
// 产品实现
public class ProductA1 implements ProductA {
@Override
public void operationA() {
System.out.println("ProductA1 operationA");
}
}
public class ProductB1 implements ProductB {
@Override
public void operationB() {
System.out.println("ProductB1 operationB");
}
}
// 抽象工厂接口 - 可创建多个产品族
public interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}
// 具体工厂1 - 创建产品族1
public class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ProductA1();
}
@Override
public ProductB createProductB() {
return new ProductB1();
}
}
// 客户端使用
public class AbstractFactoryClient {
public static void main(String[] args) {
AbstractFactory factory1 = new ConcreteFactory1();
ProductA productA1 = factory1.createProductA();
ProductB productB1 = factory1.createProductB();
productA1.operationA();
productB1.operationB();
}
}
代码解释:
- 抽象工厂模式用于创建产品族(一系列相关的产品)
- AbstractFactory接口定义创建多个产品的方法
- 客户端代码可以切换整个产品族,而不需要修改其他代码
优点:可以创建一系列相关的产品对象,符合开闭原则
2.5 工厂模式对比
| 特性 | 简单工厂 | 工厂方法 | 抽象工厂 |
|---|---|---|---|
| 产品复杂度 | 单一产品 | 单一产品族 | 多产品族 |
| 开闭原则 | 违反 | 符合 | 部分符合 |
| 代码复杂度 | 低 | 中 | 高 |
| 适用场景 | 产品类型少 | 产品种类多 | 产品族多 |
三、代理模式(Proxy Pattern)
3.1 模式概述
代理模式是结构型设计模式,其核心思想是为另一个对象提供一个替身或占位符,以控制对这个对象的访问。代理对象可以在不改变目标对象的情况下,增加额外的功能操作。
模式角色:
- Subject(真实对象的抽象):定义目标对象和代理对象的公共接口
- RealSubject(真实对象):定义代理对象所代表的真实实体
- Proxy(代理):保存一个引用使得代理可以访问实体,并提供与主题相同的接口
应用场景:远程代理、虚拟代理、安全代理、智能引用等。
3.2 静态代理
// 真实对象的抽象接口
public interface Subject {
void request();
}
// 真实对象 - 实现实际的业务逻辑
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: 处理实际请求");
}
}
// 代理类 - 在调用真实对象前后进行增强
public class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject() {
this.realSubject = new RealSubject();
}
@Override
public void request() {
// 调用真实对象之前的操作
System.out.println("ProxySubject: 调用前的准备工作");
// 调用真实对象的方法
realSubject.request();
// 调用真实对象之后的操作
System.out.println("ProxySubject: 调用后的清理工作");
}
}
// 客户端使用
public class StaticProxyClient {
public static void main(String[] args) {
Subject subject = new ProxySubject();
subject.request();
}
}
代码解释:
- Subject接口:定义代理和真实对象的公共接口
- RealSubject:实现实际业务逻辑的真实主题
- ProxySubject:代理类,在调用真实主题方法前后添加额外操作
优点:可以在不修改真实主题的情况下增加功能
缺点:需要为每个真实主题创建代理类,代码量大时难以维护
3.3 JDK动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 真实对象的抽象接口
public interface Subject {
void request();
String getData();
}
// 真实对象
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: 处理实际请求");
}
@Override
public String getData() {
return "RealSubject: 数据";
}
}
// InvocationHandler实现类 - 包含增强逻辑
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用前的增强逻辑
System.out.println("方法 " + method.getName() + " 调用前");
// 调用目标对象的方法
Object result = method.invoke(target, args);
// 调用后的增强逻辑
System.out.println("方法 " + method.getName() + " 调用后");
return result;
}
}
// JDK动态代理客户端
public class DynamicProxyClient {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
MyInvocationHandler handler = new MyInvocationHandler(realSubject);
// 创建代理对象
Subject proxySubject = (Subject) Proxy.newProxyInstance(
RealSubject.class.getClassLoader(),
RealSubject.class.getInterfaces(),
handler
);
proxySubject.request();
System.out.println("获取到的数据: " + proxySubject.getData());
}
}
代码解释:
- InvocationHandler接口:动态代理的核心,包含了增强逻辑
- invoke方法:当调用代理对象的方法时,会自动调用这个方法
- Proxy.newProxyInstance:在运行时动态创建代理对象
- 代理对象实现了目标对象的所有接口
优点:可以在运行时动态创建代理,无需为每个类编写代理类
缺点:只能代理接口,不能代理具体类
3.4 代理模式应用实例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
// 计算接口
interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
// 计算器实现
class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
}
// 计算器代理(带日志和性能监控)
class CalculatorProxy implements InvocationHandler {
private Calculator target;
public CalculatorProxy(Calculator target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
// 日志
System.out.println("方法 " + method.getName() + " 开始执行,参数: " +
Arrays.toString(args));
// 调用实际方法
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println("方法 " + method.getName() + " 执行完成,结果: " + result);
System.out.println("执行耗时: " + (endTime - startTime) + "ms");
return result;
}
}
// 代理模式应用测试
public class ProxyPatternDemo {
public static void main(String[] args) {
CalculatorImpl calculator = new CalculatorImpl();
InvocationHandler handler = new CalculatorProxy(calculator);
Calculator proxyCalculator = (Calculator) Proxy.newProxyInstance(
CalculatorImpl.class.getClassLoader(),
CalculatorImpl.class.getInterfaces(),
handler
);
// 使用代理计算器
int result1 = proxyCalculator.add(10, 5);
System.out.println("10 + 5 = " + result1);
int result2 = proxyCalculator.subtract(10, 5);
System.out.println("10 - 5 = " + result2);
}
}
运行结果:
方法 add 开始执行,参数: [10, 5]
方法 add 执行完成,结果: 15
执行耗时: 0ms
10 + 5 = 15
方法 subtract 开始执行,参数: [10, 5]
方法 subtract 执行完成,结果: 5
执行耗时: 0ms
10 - 5 = 5
代码解释:
- CalculatorProxy为Calculator添加了日志和性能监控功能
- 客户端使用代理对象调用方法,无需修改原有代码
- 代理模式实现了业务逻辑和增强逻辑的分离
四、模式对比与总结
4.1 三种模式对比
| 模式 | 类型 | 核心作用 | 优点 | 缺点 |
|---|---|---|---|---|
| 单例模式 | 创建型 | 确保唯一实例 | 控制资源、全局访问 | 可能过度使用 |
| 工厂模式 | 创建型 | 封装对象创建 | 解耦、符合开闭原则 | 增加类数量 |
| 代理模式 | 结构型 | 控制对象访问 | 增强功能、安全控制 | 增加调用复杂度 |
4.2 使用建议
单例模式:用于确实只需要一个实例的场景,推荐使用双重检查锁定或静态内部类实现。
工厂模式:当对象创建逻辑复杂时使用,根据复杂度选择简单工厂、工厂方法或抽象工厂,优先考虑工厂方法模式。
代理模式:需要在访问对象时添加额外功能时使用,优先使用JDK动态代理,无法代理类时使用CGLIB。
4.3 模式组合使用
在实际开发中,这三种模式经常组合使用:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
// 代理工厂(单例 + 工厂 + 代理)
public class ProxyFactory {
// 单例
private static ProxyFactory instance;
private ProxyFactory() {
}
public static ProxyFactory getInstance() {
if (instance == null) {
synchronized (ProxyFactory.class) {
if (instance == null) {
instance = new ProxyFactory();
}
}
}
return instance;
}
// 工厂方法创建代理
public <T> T createProxy(T target, InvocationHandler handler) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
}
}
结语
设计模式是软件开发中的重要工具,合理使用可以提高代码的可维护性、可扩展性和可读性。本文详细介绍了单例模式、工厂模式和代理模式的核心概念、实现方式和应用场景。通过大量的代码示例和详细解释,相信您已经对这三个模式有了深入的理解。在实际开发中,应该根据具体场景选择合适的模式,并注意模式组合使用时的权衡和取舍