23种经典设计模式是基础,但实际工作中,我们常用的也就10种左右。今天就按"创建型、结构型、行为型"三大分类,结合Java实战场景,把每个常用模式讲透,新手也能快速上手。
一、先搞懂:设计模式的核心意义
很多新手会问:"不用设计模式,代码也能跑,为什么非要学?"
举个简单例子:如果写一个用户登录功能,不考虑设计模式,可能会把"验证逻辑、数据库查询、返回结果处理"全写在一个方法里------后续要加"验证码验证""第三方登录",只能硬改原代码,改着改着就成了"祖传屎山"。
而设计模式的核心,就是"解耦":让代码的各个模块各司其职,修改一个模块不影响其他模块,同时提升复用性。记住一句话:设计模式不是"炫技",是解决实际问题的工具。
二、创建型模式:管"对象怎么创建"
创建型模式的核心作用:简化对象创建流程、控制对象创建数量、隐藏对象创建的复杂逻辑,让我们不用关注"怎么new对象",只关注"对象怎么用"。Java中最常用的有4种。
1. 单例模式(Singleton)【必用+必考】
核心需求:确保一个类在整个程序运行期间,只有一个实例对象(比如数据库连接池、日志工具类,多实例会造成资源浪费或数据混乱)。
关键注意点:线程安全、防止反射破坏、防止序列化破坏。
Java实战代码(推荐双重检查锁式,兼顾效率和安全):
java
public class Singleton {
// volatile防止指令重排,保证多线程下可见性
private static volatile Singleton instance;
// 私有构造方法,禁止外部new
private Singleton() {}
// 双重检查锁,第一次检查避免频繁加锁,第二次检查防止多线程并发创建
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
应用场景:Spring中的Bean默认是单例(非懒加载)、Redis客户端连接池、日志工具类(Logback的LoggerFactory)。
2. 工厂模式(Factory)【必用】
工厂模式分3种(简单工厂、工厂方法、抽象工厂),Java开发中最常用"简单工厂"和"工厂方法",核心是"用工厂代替new,统一管理对象创建"。
简单工厂模式(核心:一个工厂造所有对象):
场景:比如我们有"用户登录""管理员登录"两种登录方式,用工厂统一创建登录对象,不用在业务代码中频繁new。
java
// 登录接口
public interface Login {
boolean login(String username, String password);
}
// 用户登录实现
public class UserLogin implements Login {
@Override
public boolean login(String username, String password) {
// 模拟用户登录逻辑
return "user".equals(username) && "123456".equals(password);
}
}
// 管理员登录实现
public class AdminLogin implements Login {
@Override
public boolean login(String username, String password) {
// 模拟管理员登录逻辑
return "admin".equals(username) && "admin123".equals(password);
}
}
// 简单工厂:统一创建登录对象
public class LoginFactory {
public static Login getLogin(String type) {
if ("user".equals(type)) {
return new UserLogin();
} else if ("admin".equals(type)) {
return new AdminLogin();
}
throw new IllegalArgumentException("无效的登录类型");
}
}
// 业务使用(不用new,直接找工厂要)
public class Test {
public static void main(String[] args) {
Login userLogin = LoginFactory.getLogin("user");
userLogin.login("user", "123456");
}
}
工厂方法模式(核心:一个工厂造一种对象,解耦更彻底):
场景:如果后续要加"第三方登录(微信、QQ)",简单工厂会不断修改if-else,工厂方法则通过"新增工厂类"实现扩展,符合"开闭原则"(对扩展开放,对修改关闭)。
应用场景:Spring的BeanFactory(创建Bean的工厂)、MyBatis的SqlSessionFactory(创建SqlSession)。
3. 建造者模式(Builder)【常用】
核心需求:当一个类的属性很多(比如实体类有10+个字段),创建对象时,参数太多容易出错,且代码可读性差------建造者模式可以"分步构建对象",让创建过程更清晰。
Java实战代码(Lombok的@Builder注解底层就是这个逻辑):
java
public class User {
private String username;
private String password;
private Integer age;
private String phone;
// 私有构造方法,只能通过建造者创建
private User(Builder builder) {
this.username = builder.username;
this.password = builder.password;
this.age = builder.age;
this.phone = builder.phone;
}
// 建造者内部类
public static class Builder {
private String username;
private String password;
private Integer age;
private String phone;
// 链式调用,每个方法返回自身
public Builder username(String username) {
this.username = username;
return this;
}
public Builder password(String password) {
this.password = password;
return this;
}
public Builder age(Integer age) {
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
// 最终构建对象
public User build() {
return new User(this);
}
}
}
// 业务使用(链式调用,清晰易懂)
public class Test {
public static void main(String[] args) {
User user = new User.Builder()
.username("zhangsan")
.password("123456")
.age(20)
.phone("13800138000")
.build();
}
}
应用场景:实体类创建(尤其是字段多的情况)、MyBatis的QueryWrapper(链式构建查询条件)、OkHttp的请求构建。
4. 原型模式(Prototype)【场景化常用】
核心需求:当一个对象创建成本很高(比如需要查询数据库、调用接口获取数据),此时如果需要多个相同/相似的对象,直接new会重复消耗资源------原型模式通过"复制对象"(浅拷贝/深拷贝),减少资源消耗。
关键注意点:Java中实现Cloneable接口,重写clone()方法;浅拷贝只复制基本类型,深拷贝需要复制引用类型(比如List、对象)。
应用场景:Spring的Bean的作用域(prototype原型模式,每次获取都是新实例,但创建逻辑复用)、批量创建相同结构的对象(比如批量生成用户测试数据)。
三、结构型模式:管"类/对象怎么组合"
结构型模式的核心作用:将类或对象组合成更灵活、更可扩展的结构,解决"如何组装现有模块"的问题,核心是"复用现有代码"。Java中最常用的有4种。
1. 代理模式(Proxy)【必用+必考】
核心需求:给一个对象提供"代理对象",通过代理对象控制对原对象的访问------可以在原对象的方法执行前后,添加额外逻辑(比如日志、权限校验、事务控制),这也是AOP的核心原理。
Java中代理分3种:静态代理、动态代理(JDK代理、CGLIB代理),最常用的是动态代理(不用手动写代理类,自动生成)。
JDK动态代理实战(基于接口,Spring AOP默认使用):
java
// 目标接口(被代理的接口)
public interface UserService {
void addUser(String username);
}
// 目标实现类(原对象)
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("新增用户:" + username);
}
}
// 动态代理处理器(添加额外逻辑)
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;
}
}
// 测试动态代理
public class Test {
public static void main(String[] args) {
// 1. 创建目标对象
UserService userService = new UserServiceImpl();
// 2. 创建代理处理器
MyInvocationHandler handler = new MyInvocationHandler(userService);
// 3. 生成代理对象(JDK自动生成)
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
handler
);
// 4. 调用代理对象的方法(会触发invoke方法)
proxy.addUser("zhangsan");
}
}
应用场景:Spring AOP(日志、事务、权限控制)、MyBatis的Mapper代理(不用写实现类,自动生成代理对象)、Redis缓存代理。
2. 装饰器模式(Decorator)【常用】
核心需求:给一个对象动态添加额外功能,且不修改原对象的代码------和代理模式的区别:装饰器模式侧重"增强功能",代理模式侧重"控制访问"。
Java实战代码(比如给IO流添加缓冲功能):
java
// 核心接口(被装饰的对象接口)
public interface Drink {
String getName(); // 饮品名称
double getPrice(); // 饮品价格
}
// 基础实现类(被装饰的原对象)
public class Coffee implements Drink {
@Override
public String getName() {
return "纯咖啡";
}
@Override
public double getPrice() {
return 10.0;
}
}
// 装饰器抽象类(实现Drink接口,持有被装饰对象)
public abstract class DrinkDecorator implements Drink {
protected Drink drink;
public DrinkDecorator(Drink drink) {
this.drink = drink;
}
@Override
public String getName() {
return drink.getName();
}
@Override
public double getPrice() {
return drink.getPrice();
}
}
// 具体装饰器1:加牛奶
public class MilkDecorator extends DrinkDecorator {
public MilkDecorator(Drink drink) {
super(drink);
}
@Override
public String getName() {
return drink.getName() + "+牛奶";
}
@Override
public double getPrice() {
return drink.getPrice() + 3.0; // 加牛奶加3元
}
}
// 具体装饰器2:加糖
public class SugarDecorator extends DrinkDecorator {
public SugarDecorator(Drink drink) {
super(drink);
}
@Override
public String getName() {
return drink.getName() + "+糖";
}
@Override
public double getPrice() {
return drink.getPrice() + 1.0; // 加糖加1元
}
}
// 测试装饰器
public class Test {
public static void main(String[] args) {
// 基础纯咖啡
Drink coffee = new Coffee();
System.out.println(coffee.getName() + ":" + coffee.getPrice() + "元");
// 加牛奶
Drink milkCoffee = new MilkDecorator(coffee);
System.out.println(milkCoffee.getName() + ":" + milkCoffee.getPrice() + "元");
// 加牛奶+糖(嵌套装饰)
Drink milkSugarCoffee = new SugarDecorator(milkCoffee);
System.out.println(milkSugarCoffee.getName() + ":" + milkSugarCoffee.getPrice() + "元");
}
}
应用场景:Java IO流(BufferedInputStream装饰FileInputStream,添加缓冲功能)、Spring的BeanWrapper(装饰Bean,添加属性编辑功能)。
3. 适配器模式(Adapter)【场景化常用】
核心需求:解决"两个接口不兼容,无法直接调用"的问题------通过适配器,将一个接口转换成另一个接口,让原本不能一起工作的类可以一起工作。
例子:比如我们有一个老系统的"支付接口"(返回String类型结果),新系统需要"支付接口"返回Boolean类型结果,此时就可以用适配器转换。
应用场景:老系统改造(兼容新接口)、第三方接口适配(比如不同支付平台的接口统一适配成自己系统的接口)、Java的Arrays.asList()(将数组适配成List)。
4. 组合模式(Composite)【场景化常用】
核心需求:将"单个对象"和"对象集合"统一对待,形成树形结构------比如文件夹和文件,文件夹里可以放文件,也可以放子文件夹,我们可以统一操作"文件夹"和"文件"。
应用场景:树形结构展示(比如菜单树、部门树)、文件系统管理、MyBatis的SQL节点(比如<if><where>标签,既是单个节点,也能组合成复杂SQL)。
四、行为型模式:管"对象之间怎么通信、协作"
行为型模式的核心作用:解决"对象之间的交互逻辑",让对象之间的通信更灵活、更解耦,避免出现"一个对象依赖多个对象"的情况。Java中最常用的有3种。
1. 策略模式(Strategy)【必用】
核心需求:定义一系列算法,将每个算法封装起来,并且可以互相替换------比如一个支付功能,有"微信支付""支付宝支付""银行卡支付",算法不同,但调用方式一致,方便切换。
Java实战代码:
java
// 策略接口(定义算法的统一方法)
public interface PayStrategy {
void pay(double money); // 支付方法
}
// 具体策略1:微信支付
public class WeChatPay implements PayStrategy {
@Override
public void pay(double money) {
System.out.println("微信支付:" + money + "元");
}
}
// 具体策略2:支付宝支付
public class Alipay implements PayStrategy {
@Override
public void pay(double money) {
System.out.println("支付宝支付:" + money + "元");
}
}
// 策略上下文(统一调用策略,切换策略)
public class PayContext {
private PayStrategy payStrategy;
// 设置策略(可以动态切换)
public void setPayStrategy(PayStrategy payStrategy) {
this.payStrategy = payStrategy;
}
// 统一调用支付方法
public void pay(double money) {
payStrategy.pay(money);
}
}
// 测试策略模式
public class Test {
public static void main(String[] args) {
PayContext payContext = new PayContext();
// 切换微信支付
payContext.setPayStrategy(new WeChatPay());
payContext.pay(100.0);
// 切换支付宝支付
payContext.setPayStrategy(new Alipay());
payContext.pay(200.0);
}
}
应用场景:支付方式切换、排序算法切换(比如ArrayList的sort方法,可传入不同的Comparator策略)、Spring的BeanPostProcessor(不同的后置处理器策略)。
2. 观察者模式(Observer)【常用】
核心需求:定义"一对多"的依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)都会自动收到通知并更新------比如"发布-订阅"模式。
Java实战代码(Java自带Observer和Observable接口):
java
// 被观察者(主题):比如公众号
public class WeChatPublic extends Observable {
private String message;
public void setMessage(String message) {
this.message = message;
setChanged(); // 标记状态已变化
notifyObservers(message); // 通知所有观察者
}
}
// 观察者1:用户A
public class UserA implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("用户A收到公众号消息:" + arg);
}
}
// 观察者2:用户B
public class UserB implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("用户B收到公众号消息:" + arg);
}
}
// 测试观察者模式
public class Test {
public static void main(String[] args) {
// 创建被观察者(公众号)
WeChatPublic publicAccount = new WeChatPublic();
// 注册观察者(用户关注公众号)
publicAccount.addObserver(new UserA());
publicAccount.addObserver(new UserB());
// 发布消息(被观察者状态变化)
publicAccount.setMessage("Java设计模式详解更新啦!");
}
}
应用场景:Spring的事件驱动模型(ApplicationEvent和ApplicationListener)、消息队列(Kafka、RabbitMQ的发布-订阅模式)、GUI界面的事件监听(比如按钮点击事件)。
3. 模板方法模式(Template Method)【常用】
核心需求:定义一个方法的"骨架",将方法中的某些步骤延迟到子类中实现------比如一个"登录流程",步骤是"验证账号密码→处理登录→记录日志",其中"验证账号密码"和"处理登录"因用户类型不同而不同,"记录日志"是通用步骤,就可以用模板方法。
Java实战代码:
java
// 模板抽象类(定义登录流程骨架)
public abstract class LoginTemplate {
// 模板方法:定义登录流程,不可修改
public final void login(String username, String password) {
// 步骤1:验证账号密码(子类实现)
boolean check = checkUsernameAndPassword(username, password);
if (!check) {
System.out.println("账号密码错误");
return;
}
// 步骤2:处理登录(子类实现)
handleLogin(username);
// 步骤3:记录日志(通用步骤,父类实现)
logLogin(username);
}
// 抽象方法:验证账号密码(子类实现)
protected abstract boolean checkUsernameAndPassword(String username, String password);
// 抽象方法:处理登录(子类实现)
protected abstract void handleLogin(String username);
// 具体方法:记录日志(通用步骤)
private void logLogin(String username) {
System.out.println(username + "登录成功,时间:" + new Date());
}
}
// 子类1:用户登录
public class UserLoginTemplate extends LoginTemplate {
@Override
protected boolean checkUsernameAndPassword(String username, String password) {
return "user".equals(username) && "123456".equals(password);
}
@Override
protected void handleLogin(String username) {
System.out.println("用户" + username + "登录成功,跳转到用户首页");
}
}
// 子类2:管理员登录
public class AdminLoginTemplate extends LoginTemplate {
@Override
protected boolean checkUsernameAndPassword(String username, String password) {
return "admin".equals(username) && "admin123".equals(password);
}
@Override
protected void handleLogin(String username) {
System.out.println("管理员" + username + "登录成功,跳转到管理员后台");
}
}
// 测试模板方法
public class Test {
public static void main(String[] args) {
// 用户登录
LoginTemplate userLogin = new UserLoginTemplate();
userLogin.login("user", "123456");
// 管理员登录
LoginTemplate adminLogin = new AdminLoginTemplate();
adminLogin.login("admin", "admin123");
}
}
应用场景:Spring的JdbcTemplate(定义JDBC操作骨架,子类实现具体的SQL执行)、JUnit的TestCase(定义测试流程骨架)、各种框架的"模板类"(统一流程,灵活扩展)。
五、最后:设计模式的使用误区
很多新手学完设计模式,会陷入"为了用设计模式而用设计模式"的误区,记住3个原则,避免踩坑:
-
不要过度设计:如果业务简单(比如一个简单的工具类),不用强行用设计模式,否则会增加代码复杂度。
-
优先满足业务:设计模式是工具,不是目的,先保证业务能正常运行,再考虑用设计模式优化。
-
理解本质,而非死记代码:每个设计模式的核心是"解决什么问题",比如单例是"控制实例数量",代理是"增强+控制访问",理解本质才能灵活运用。
对于Java程序员来说,掌握上面这11种设计模式,足以应对日常开发和面试。后续可以结合Spring、MyBatis等框架的源码,看看框架是如何运用这些设计模式的,加深理解~