一、单例模式:玉皇大帝只能有一个
1. 什么是单例模式?
单例模式就像玉皇大帝——三界之内只能有一个。它的核心思想是:确保一个类只有一个实例,并提供一个全局访问点。
2. 为什么需要单例?
想象一下,如果天庭有多个玉皇大帝:
- 一个说“下雨”,一个说“晴天”
- 一个要蟠桃会,一个要取消
- 神仙们不知道该听谁的,三界大乱!
在编程中,类似场景有:
3. 实现方式(懒汉式)
public class Emperor {
// 私有静态实例
private static Emperor instance;
// 私有构造,防止外部new
private Emperor() {
System.out.println("朕登基了!");
}
// 公有获取方法
public static Emperor getInstance() {
if (instance == null) {
instance = new Emperor();
}
return instance;
}
public void rule() {
System.out.println("朕在治理天下");
}
}
// 使用
public class Kingdom {
public static void main(String[] args) {
Emperor e1 = Emperor.getInstance();
Emperor e2 = Emperor.getInstance();
System.out.println("是同一个皇帝吗?" + (e1 == e2)); // true
}
}
4. 线程安全改进版
上面的简单实现有个大问题:线程不安全!如果两个神仙同时要见玉帝,可能会创建两个实例。
改进方案(双重检查锁定):
public class SafeEmperor {
// volatile保证可见性
private static volatile SafeEmperor instance;
private SafeEmperor() {
System.out.println("朕安全登基了!");
}
public static SafeEmperor getInstance() {
if (instance == null) { // 第一次检查
synchronized (SafeEmperor.class) { // 加锁
if (instance == null) { // 第二次检查
instance = new SafeEmperor();
}
}
}
return instance;
}
}
5. 枚举实现(最推荐)
public enum BestEmperor {
INSTANCE;
public void rule() {
System.out.println("朕用枚举登基,最安全!");
}
}
// 使用
BestEmperor emperor = BestEmperor.INSTANCE;
emperor.rule();
为什么枚举最安全?
- 线程安全(JVM保证)
- 防止反射攻击
- 防止序列化破坏单例
- 代码最简洁
6. 优缺点分析
优点:
- 严格控制实例数量:确保全局唯一
- 节省资源:避免频繁创建销毁
- 全局访问点:方便统一管理
缺点:
- 没有接口:难以扩展
- 与单一职责冲突:既管创建又管业务
- 多线程需额外处理:要考虑线程安全,尤其是在早期的懒汉式实现中
- 测试困难:难以模拟替代
二、模板方法:修仙的标准化流程
1. 什么是模板方法?
想象一下修仙的流程:
- 打坐调息
- 运转周天
- 突破瓶颈
- 巩固境界
每个门派(类)的具体修炼方法不同,但流程(模板)是一样的。这就是模板方法模式!
2. 核心思想
定义算法骨架,将具体步骤延迟到子类实现。就像修仙秘籍,总纲固定,具体心法各派不同。
3. 代码示例
// 抽象模板类
abstract class CultivationProcess {
// 模板方法(final防止子类修改流程)
public final void cultivate() {
meditate(); // 打坐调息
circulateQi(); // 运转周天
breakthrough(); // 突破瓶颈
consolidate(); // 巩固境界
if (hook()) { // 钩子方法
specialTechnique();
}
}
// 具体方法(已有默认实现)
private void meditate() {
System.out.println("静心打坐,调整呼吸...");
}
// 抽象方法(子类必须实现)
protected abstract void circulateQi();
protected abstract void breakthrough();
// 具体方法(已有默认实现)
private void consolidate() {
System.out.println("巩固境界,稳定修为");
}
// 钩子方法(子类可选覆盖)
protected boolean hook() {
return false;
}
protected void specialTechnique() {
// 空实现,子类可覆盖
}
}
// 具体子类:蜀山剑派
class ShuShanSchool extends CultivationProcess {
@Override
protected void circulateQi() {
System.out.println("运转蜀山心法,剑气纵横");
}
@Override
protected void breakthrough() {
System.out.println("剑意通明,突破剑心境");
}
@Override
protected boolean hook() {
return true; // 需要特殊技法
}
@Override
protected void specialTechnique() {
System.out.println("施展御剑术,千里取敌首");
}
}
// 具体子类:少林寺
class ShaolinTemple extends CultivationProcess {
@Override
protected void circulateQi() {
System.out.println("运转易筋经,内力如潮");
}
@Override
protected void breakthrough() {
System.out.println("金刚不坏,突破罗汉境");
}
// 不覆盖hook(),使用默认值
}
// 使用
public class CultivationTest {
public static void main(String[] args) {
CultivationProcess shushan = new ShuShanSchool();
System.out.println("=== 蜀山弟子修炼 ===");
shushan.cultivate();
System.out.println("\n=== 少林弟子修炼 ===");
CultivationProcess shaolin = new ShaolinTemple();
shaolin.cultivate();
}
}
4. 模板方法的组成
- 模板方法:
cultivate(),定义算法骨架
- 具体方法:
meditate(),已有实现的方法
- 抽象方法:
circulateQi(),子类必须实现
- 钩子方法:
hook(),子类可选覆盖,影响流程
5. 优缺点分析
优点:
- 代码复用:公共部分在父类,避免重复
- 扩展性好:新增子类容易,符合开闭原则
- 反向控制:父类调用子类,好莱坞原则
- 便于维护:修改只需改父类
缺点:
- 子类影响父类:子类实现影响整体算法
- 复杂度增加:类层次变深
- 继承的局限性:Java单继承
6. 反例:没有使用模板方法
// 反例:重复代码
class ShuShanCultivation {
public void cultivate() {
System.out.println("静心打坐,调整呼吸...");
System.out.println("运转蜀山心法,剑气纵横");
System.out.println("剑意通明,突破剑心境");
System.out.println("巩固境界,稳定修为");
System.out.println("施展御剑术,千里取敌首");
}
}
class ShaolinCultivation {
public void cultivate() {
System.out.println("静心打坐,调整呼吸..."); // 重复!
System.out.println("运转易筋经,内力如潮");
System.out.println("金刚不坏,突破罗汉境");
System.out.println("巩固境界,稳定修为"); // 重复!
}
}
问题:大量重复代码,修改时要改多个地方,容易出错。
三、封装实现三兄弟
1. 代理模式:找个替身帮你干活
概念理解
代理模式就像明星和经纪人的关系:
- 明星:真正干活的(真实对象)
- 经纪人:处理杂事的(代理对象)
- 你:客户,只和经纪人打交道
静态代理示例
// 1. 共同接口
interface Star {
void sing();
void act();
}
// 2. 真实对象(周杰伦)
class JayChou implements Star {
@Override
public void sing() {
System.out.println("周杰伦演唱《青花瓷》");
}
@Override
public void act() {
System.out.println("周杰伦拍摄电影");
}
}
// 3. 代理对象(经纪人)
class StarAgent implements Star {
private Star star; // 持有真实对象的引用
public StarAgent(Star star) {
this.star = star;
}
@Override
public void sing() {
System.out.println("经纪人:安排档期、谈价格、签合同");
star.sing(); // 调用真实对象的方法
System.out.println("经纪人:收钱、处理后续事宜");
}
@Override
public void act() {
System.out.println("经纪人:筛选剧本、安排档期");
star.act();
System.out.println("经纪人:结算片酬");
}
// 经纪人自己的方法
public void arrangeInterview() {
System.out.println("经纪人:安排媒体采访");
}
}
// 使用
public class ProxyDemo {
public static void main(String[] args) {
Star jay = new JayChou();
Star agent = new StarAgent(jay);
System.out.println("=== 演唱会 ===");
agent.sing();
System.out.println("\n=== 拍电影 ===");
agent.act();
}
}
动态代理(JDK动态代理)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 动态代理处理器
class DynamicAgent implements InvocationHandler {
private Object target; // 真实对象
public DynamicAgent(Object target) {
this.target = target;
}
// 创建代理对象
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理:方法执行前处理");
Object result = method.invoke(target, args);
System.out.println("动态代理:方法执行后处理");
return result;
}
}
// 使用
public class DynamicProxyDemo {
public static void main(String[] args) {
Star jay = new JayChou();
DynamicAgent agent = new DynamicAgent(jay);
Star proxy = (Star) agent.getProxy();
proxy.sing();
proxy.act();
}
}
代理模式优缺点
优点:
- 职责清晰:真实角色专注业务,代理处理其他事务
- 高扩展性:代理类可以在不修改目标对象的基础上扩展功能
- 智能化:动态代理更灵活
缺点:
- 速度慢:代理调用比直接调用慢
- 复杂度增加:增加代理层,系统变复杂
2. 状态模式:对象的心情会变
概念理解
状态模式就像人的情绪:
- 开心时:唱歌跳舞
- 生气时:大喊大叫
- 伤心时:默默流泪
同一个对象(人),不同状态(情绪),行为不同。
代码示例
// 1. 状态接口
interface EmotionState {
void express();
EmotionState nextState();
}
// 2. 具体状态:开心
class HappyState implements EmotionState {
@Override
public void express() {
System.out.println("😊 开心:唱歌跳舞,见谁都笑");
}
@Override
public EmotionState nextState() {
System.out.println("乐极生悲,转为伤心");
return new SadState();
}
}
// 3. 具体状态:生气
class AngryState implements EmotionState {
@Override
public void express() {
System.out.println("😠 生气:大喊大叫,摔东西");
}
@Override
public EmotionState nextState() {
System.out.println("发泄完了,转为平静");
return new CalmState();
}
}
// 4. 具体状态:伤心
class SadState implements EmotionState {
@Override
public void express() {
System.out.println("😢 伤心:默默流泪,不想说话");
}
@Override
public EmotionState nextState() {
System.out.println("时间治愈,转为开心");
return new HappyState();
}
}
// 5. 具体状态:平静
class CalmState implements EmotionState {
@Override
public void express() {
System.out.println("😐 平静:正常生活,情绪稳定");
}
@Override
public EmotionState nextState() {
System.out.println("遇到好事,转为开心");
return new HappyState();
}
}
// 6. 上下文(人)
class Person {
private EmotionState currentState;
public Person() {
this.currentState = new CalmState(); // 初始状态
}
public void setState(EmotionState state) {
this.currentState = state;
}
public void expressEmotion() {
currentState.express();
}
public void changeToNextState() {
this.currentState = currentState.nextState();
}
}
// 使用
public class StatePatternDemo {
public static void main(String[] args) {
Person person = new Person();
System.out.println("=== 初始状态 ===");
person.expressEmotion();
System.out.println("\n=== 切换到开心 ===");
person.setState(new HappyState());
person.expressEmotion();
System.out.println("\n=== 自动转换到下一状态 ===");
person.changeToNextState();
person.expressEmotion();
}
}
状态模式优缺点
优点:
- 封装状态转换:状态转换逻辑封装在状态类中
- 消除条件语句:避免大量的if-else或switch-case
- 符合开闭原则:新增状态容易,不影响其他状态
- 状态局部化:每个状态的行为集中在一个类中
缺点:
- 类数量多:每个状态一个类,类爆炸
- 结构复杂:状态转换逻辑分散在各个状态类
3. 状态机模式:更复杂的状态管理
与状态模式的区别
状态模式关注当前状态的行为,状态机模式关注状态之间的转换规则。
就像自动售货机:
- 状态:待机、选择商品、付款、出货、找零
- 事件:投币、选择商品、确认、取消
- 转换规则:投币后从待机到选择商品
状态机示例(简化版)
import java.util.HashMap;
import java.util.Map;
// 1. 状态枚举
enum VendingState {
IDLE, // 待机
SELECTING, // 选择商品
PAYING, // 付款中
DELIVERING, // 出货中
RETURNING_CHANGE // 找零
}
// 2. 事件枚举
enum Event {
INSERT_COIN, // 投币
SELECT_ITEM, // 选择商品
CONFIRM_PAY, // 确认支付
CANCEL, // 取消
COMPLETE // 完成
}
// 3. 状态机
class VendingMachine {
private VendingState currentState;
private Map<VendingState, Map<Event, VendingState>> transitions;
public VendingMachine() {
this.currentState = VendingState.IDLE;
initTransitions();
}
private void initTransitions() {
transitions = new HashMap<>();
// IDLE状态的转换
Map<Event, VendingState> idleTransitions = new HashMap<>();
idleTransitions.put(Event.INSERT_COIN, VendingState.SELECTING);
transitions.put(VendingState.IDLE, idleTransitions);
// SELECTING状态的转换
Map<Event, VendingState> selectingTransitions = new HashMap<>();
selectingTransitions.put(Event.SELECT_ITEM, VendingState.PAYING);
selectingTransitions.put(Event.CANCEL, VendingState.IDLE);
transitions.put(VendingState.SELECTING, selectingTransitions);
// PAYING状态的转换
Map<Event, VendingState> payingTransitions = new HashMap<>();
payingTransitions.put(Event.CONFIRM_PAY, VendingState.DELIVERING);
payingTransitions.put(Event.CANCEL, VendingState.RETURNING_CHANGE);
transitions.put(VendingState.PAYING, payingTransitions);
// DELIVERING状态的转换
Map<Event, VendingState> deliveringTransitions = new HashMap<>();
deliveringTransitions.put(Event.COMPLETE, VendingState.RETURNING_CHANGE);
transitions.put(VendingState.DELIVERING, deliveringTransitions);
// RETURNING_CHANGE状态的转换
Map<Event, VendingState> returningTransitions = new HashMap<>();
returningTransitions.put(Event.COMPLETE, VendingState.IDLE);
transitions.put(VendingState.RETURNING_CHANGE, returningTransitions);
}
// 处理事件
public void handleEvent(Event event) {
Map<Event, VendingState> stateTransitions = transitions.get(currentState);
if (stateTransitions != null && stateTransitions.containsKey(event)) {
VendingState oldState = currentState;
currentState = stateTransitions.get(event);
System.out.printf("状态转换:%s --[%s]--> %s%n",
oldState, event, currentState);
executeStateAction();
} else {
System.out.printf("错误:在状态 %s 下不能处理事件 %s%n",
currentState, event);
}
}
private void executeStateAction() {
switch (currentState) {
case IDLE:
System.out.println("动作:等待投币");
break;
case SELECTING:
System.out.println("动作:请选择商品");
break;
case PAYING:
System.out.println("动作:请确认支付");
break;
case DELIVERING:
System.out.println("动作:正在出货...");
break;
case RETURNING_CHANGE:
System.out.println("动作:正在找零...");
break;
}
}
public VendingState getCurrentState() {
return currentState;
}
}
// 使用
public class StateMachineDemo {
public static void main(String[] args) {
VendingMachine vm = new VendingMachine();
System.out.println("=== 正常流程 ===");
vm.handleEvent(Event.INSERT_COIN); // 投币
vm.handleEvent(Event.SELECT_ITEM); // 选择商品
vm.handleEvent(Event.CONFIRM_PAY); // 确认支付
vm.handleEvent(Event.COMPLETE); // 出货完成
vm.handleEvent(Event.COMPLETE); // 找零完成
System.out.println("\n=== 取消流程 ===");
vm.handleEvent(Event.INSERT_COIN); // 投币
vm.handleEvent(Event.SELECT_ITEM); // 选择商品
vm.handleEvent(Event.CANCEL); // 取消
vm.handleEvent(Event.COMPLETE); // 找零完成
System.out.println("\n=== 错误事件 ===");
vm.handleEvent(Event.CONFIRM_PAY); // 错误:应该在PAYING状态
}
}
状态机模式优缺点
优点:
- 状态转换明确:转换规则清晰可见
- 易于维护:状态和转换集中管理
- 防止非法状态:只能按定义转换
- 可视化:状态图容易绘制和理解
缺点:
- 复杂度高:状态多时转换表庞大
- 不灵活:硬编码转换规则,修改麻烦
- 可能过度设计:简单场景用状态模式即可
四、实战对比与选择
什么时候用什么模式?
| 场景 |
推荐模式 |
理由 |
| 需要全局唯一对象 |
单例模式 |
确保唯一性 |
| 算法流程固定,步骤可变 |
模板方法 |
复用公共逻辑 |
| 需要控制对象访问 |
代理模式 |
添加额外功能 |
| 对象行为随状态改变 |
状态模式 |
消除条件分支 |
| 状态转换复杂且有规则 |
状态机模式 |
明确转换逻辑 |
组合使用示例:修仙系统的状态机
// 结合状态模式和状态机
interface CultivationState {
void practice();
CultivationState breakthrough(); // 突破到下一境界
CultivationState getDeviation(); // 走火入魔
}
class QiRefiningState implements CultivationState {
@Override
public void practice() {
System.out.println("练气期:引气入体,打通经脉");
}
@Override
public CultivationState breakthrough() {
System.out.println("突破!进入筑基期");
return new FoundationState();
}
@Override
public CultivationState getDeviation() {
System.out.println("走火入魔!修为尽废");
return new MortalState();
}
}
class FoundationState implements CultivationState {
@Override
public void practice() {
System.out.println("筑基期:巩固根基,筑基成台");
}
@Override
public CultivationState breakthrough() {
System.out.println("突破!进入金丹期");
return new GoldenCoreState();
}
@Override
public CultivationState getDeviation() {
System.out.println("走火入魔!退回练气期");
return new QiRefiningState();
}
}
// 状态机管理
class Cultivator {
private CultivationState currentState;
public Cultivator() {
this.currentState = new QiRefiningState();
}
public void practice() {
currentState.practice();
}
public void breakthrough() {
this.currentState = currentState.breakthrough();
}
public void getDeviation() {
this.currentState = currentState.getDeviation();
}
public void showState() {
System.out.println("当前境界:" + currentState.getClass().getSimpleName());
}
}
五、总结
今天我们探讨了Java中几个核心的设计模式:单例模式、模板方法、代理模式、状态模式以及状态机模式。它们是构建健壮、可维护软件架构的重要Java基础。
- 单例模式:确保唯一,像玉皇大帝一样独一无二,常用于管理全局资源。
- 模板方法:固定流程,像修仙秘籍的总纲,将不变的行为提升到父类,可变部分交给子类。
- 代理模式:找个替身,像明星的经纪人,在不改变原始对象的情况下增加额外功能。
- 状态模式:心情多变,像人的情绪变化,将状态相关的行为封装到独立的类中,消除庞大的条件判断。
- 状态机模式:规则明确,像自动售货机的状态转换,适用于状态转换逻辑复杂的场景。
掌握这些模式的关键在于理解其适用场景:
- 单例要线程安全,枚举实现是最佳实践。
- 模板要留钩子,给子类灵活调整的空间。
- 代理要透明,用户无需关心背后的真实对象。
- 状态要封装,避免使用冗长的if-else语句来判断对象状态。
- 状态机要清晰,明确定义状态转换规则,适用于工作流等复杂场景。
设计模式是解决特定问题的经验总结,而非教条。正确运用它们可以提升代码质量,但过度设计也会增加复杂度。深入理解设计模式的思想,并在实际开发中灵活运用,方能写出优雅且易于维护的代码。