找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2152

积分

0

好友

308

主题
发表于 7 天前 | 查看: 18| 回复: 0

一、单例模式:玉皇大帝只能有一个

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();

为什么枚举最安全?

  1. 线程安全(JVM保证)
  2. 防止反射攻击
  3. 防止序列化破坏单例
  4. 代码最简洁

6. 优缺点分析

优点

  1. 严格控制实例数量:确保全局唯一
  2. 节省资源:避免频繁创建销毁
  3. 全局访问点:方便统一管理

缺点

  1. 没有接口:难以扩展
  2. 与单一职责冲突:既管创建又管业务
  3. 多线程需额外处理:要考虑线程安全,尤其是在早期的懒汉式实现中
  4. 测试困难:难以模拟替代

二、模板方法:修仙的标准化流程

1. 什么是模板方法?

想象一下修仙的流程:

  1. 打坐调息
  2. 运转周天
  3. 突破瓶颈
  4. 巩固境界

每个门派(类)的具体修炼方法不同,但流程(模板)是一样的。这就是模板方法模式!

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. 模板方法的组成

  1. 模板方法cultivate(),定义算法骨架
  2. 具体方法meditate(),已有实现的方法
  3. 抽象方法circulateQi(),子类必须实现
  4. 钩子方法hook(),子类可选覆盖,影响流程

5. 优缺点分析

优点

  1. 代码复用:公共部分在父类,避免重复
  2. 扩展性好:新增子类容易,符合开闭原则
  3. 反向控制:父类调用子类,好莱坞原则
  4. 便于维护:修改只需改父类

缺点

  1. 子类影响父类:子类实现影响整体算法
  2. 复杂度增加:类层次变深
  3. 继承的局限性: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();
    }
}

代理模式优缺点
优点

  1. 职责清晰:真实角色专注业务,代理处理其他事务
  2. 高扩展性:代理类可以在不修改目标对象的基础上扩展功能
  3. 智能化:动态代理更灵活

缺点

  1. 速度慢:代理调用比直接调用慢
  2. 复杂度增加:增加代理层,系统变复杂

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();
    }
}

状态模式优缺点
优点

  1. 封装状态转换:状态转换逻辑封装在状态类中
  2. 消除条件语句:避免大量的if-else或switch-case
  3. 符合开闭原则:新增状态容易,不影响其他状态
  4. 状态局部化:每个状态的行为集中在一个类中

缺点

  1. 类数量多:每个状态一个类,类爆炸
  2. 结构复杂:状态转换逻辑分散在各个状态类

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状态
    }
}

状态机模式优缺点
优点

  1. 状态转换明确:转换规则清晰可见
  2. 易于维护:状态和转换集中管理
  3. 防止非法状态:只能按定义转换
  4. 可视化:状态图容易绘制和理解

缺点

  1. 复杂度高:状态多时转换表庞大
  2. 不灵活:硬编码转换规则,修改麻烦
  3. 可能过度设计:简单场景用状态模式即可

四、实战对比与选择

什么时候用什么模式?

场景 推荐模式 理由
需要全局唯一对象 单例模式 确保唯一性
算法流程固定,步骤可变 模板方法 复用公共逻辑
需要控制对象访问 代理模式 添加额外功能
对象行为随状态改变 状态模式 消除条件分支
状态转换复杂且有规则 状态机模式 明确转换逻辑

组合使用示例:修仙系统的状态机

// 结合状态模式和状态机
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基础。

  1. 单例模式:确保唯一,像玉皇大帝一样独一无二,常用于管理全局资源。
  2. 模板方法:固定流程,像修仙秘籍的总纲,将不变的行为提升到父类,可变部分交给子类。
  3. 代理模式:找个替身,像明星的经纪人,在不改变原始对象的情况下增加额外功能。
  4. 状态模式:心情多变,像人的情绪变化,将状态相关的行为封装到独立的类中,消除庞大的条件判断。
  5. 状态机模式:规则明确,像自动售货机的状态转换,适用于状态转换逻辑复杂的场景。

掌握这些模式的关键在于理解其适用场景:

  • 单例要线程安全,枚举实现是最佳实践。
  • 模板要留钩子,给子类灵活调整的空间。
  • 代理要透明,用户无需关心背后的真实对象。
  • 状态要封装,避免使用冗长的if-else语句来判断对象状态。
  • 状态机要清晰,明确定义状态转换规则,适用于工作流等复杂场景。

设计模式是解决特定问题的经验总结,而非教条。正确运用它们可以提升代码质量,但过度设计也会增加复杂度。深入理解设计模式的思想,并在实际开发中灵活运用,方能写出优雅且易于维护的代码。




上一篇:交换机性能参数全解析:背板带宽、包转发率计算公式与实例
下一篇:Kotlin协程异常处理全解析:13个实战避坑场景详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:36 , Processed in 0.313387 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表