Java 状态模式详解

Java 中的状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态发生改变时改变其行为。状态模式基于开放-封闭原则,可以将对象的状态从主体中分离出来并将其封装在独立的状态类中,从而使主体和状态之间的耦合度降低,使得系统更加灵活、可扩展和易于维护。

本文将详细介绍 Java 中的状态模式,我们将从以下几个方面进行讲解:

  1. 状态模式的概述
  2. 状态模式的结构与实现
  3. 状态模式的优缺点
  4. 状态模式的适用场景
  5. 示例程序的设计与实现

1. 状态模式的概述

状态模式是一种通过将对象的状态转换逻辑分布到状态对象中来实现状态转换的设计模式。它将对象的行为与对应的状态分离,使得在修改对象状态时,不需要修改对象的行为方法。同时,状态模式可以通过将状态的转换逻辑包含在各个状态类中来简化代码,避免出现大量的条件判断语句,从而提高代码的可读性和可维护性。

根据 GoF 的定义,状态模式的三个核心角色分别是:

  • 环境(Context):它定义了客户端所感兴趣的接口,并维护一个当前状态,在具体状态类中实现该接口的各个具体操作。
  • 抽象状态(State):它定义了一个接口,用于封装环境对象中不同状态对应的行为。
  • 具体状态(Concrete State):它实现了抽象状态接口,封装了不同状态下对环境对象的响应行为。

2. 状态模式的结构与实现

在 Java 中,状态模式的实现方法比较简单,通常可以按照以下步骤进行:

  1. 定义抽象状态接口(State),它包含了具体状态所对应的操作方法;
  2. 定义具体状态类(ConcreteState1、ConcreteState2等),它们实现了抽象状态接口,封装了具体的状态行为;
  3. 定义环境类(Context),它包含了当前状态以及对外提供的操作接口;
  4. 在环境类中,使用一个State类型的变量来表示当前状态,并在具体操作中调用该状态的方法;
  5. 当状态发生改变时,修改环境对象的状态即可。

下面是 Java 中状态模式的一个简单实现:

// 定义抽象状态接口
interface State {
    void handle();
}

// 定义具体状态类
class ConcreteState1 implements State {
    @Override
    public void handle() {
        System.out.println("当前状态为 State1.");
    }
}

class ConcreteState2 implements State {
    @Override
    public void handle() {
        System.out.println("当前状态为 State2.");
    }
}

// 定义环境类
class Context {
    private State state;

    public void setState(State state) {
        this.state = state;
    }

    public void request() {
        state.handle();
    }
}

// 示例程序
public class StatePatternDemo {
    public static void main(String[] args) {
        // 创建状态对象
        State state1 = new ConcreteState1();
        State state2 = new ConcreteState2();

        // 创建环境对象
        Context context = new Context();
        context.setState(state1);
        context.request();

        context.setState(state2);
        context.request();
    }
}

在上述代码中,我们首先定义了抽象状态接口State和两个具体状态类ConcreteState1、ConcreteState2,它们分别实现了State接口。然后,我们定义了一个包含状态切换逻辑的环境类Context,其中,使用状态对象来表示当前的状态,并在request方法中调用当前状态的handle方法。最后,我们创建一个示例程序,调用context的setState方法来改变状态,并观察其输出。

3. 状态模式的优缺点

状态模式具有如下优点:

  • 结构清晰、封装性好:将状态的转换逻辑分布到独立的状态类中,使得状态之间的耦合度降低,并且可以将状态的行为封装在状态类中,提高了系统的可维护性和可读性。
  • 扩展性好:对于新的状态,只需要创建一个具体状态类即可,同时也可以很方便地增加新的状态转换。
  • 易于维护和调试:状态模式将各个状态进行了封装,每个状态对象都只关注自身的行为,使得代码易于维护和调试。

但是状态模式也存在一些缺点:

  • 状态模式会导致系统类和对象的个数增加:状态模式将每个状态都封装成了独立的对象,因此会增加系统的复杂度和实现难度。
  • 状态模式的使用条件较为苛刻:由于状态模式要求将状态转换逻辑包含在具体状态类中,因此只适合“状态不多”且“状态转换比较少”的情况,否则会导致系统的维护和扩展变得困难。

4. 状态模式的适用场景

状态模式通常适用于以下情形:

  • 行为随状态改变而改变的场景:在状态模式中,行为是由状态决定的,因此当一个对象状态改变时,它所对应的行为也随之改变。
  • 条件、分支语句多的场景:如果使用传统的if/else语句实现状态转换逻辑,通常会出现大量的条件语句,从而导致代码复杂度的提高,而状态模式可以很好地解决这个问题。

具体来说,状态模式通常适用于以下场景:

  • 对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为;
  • 某个操作有多个状态,且这些状态之间可以相互转换;
  • 在不同状态下执行的操作有大量重复代码时,可以将该重复代码封装在具体状态类中,从而提高代码的重用性和可维护性。

5. 示例程序的设计与实现

下面我们将使用一个简单示例来说明状态模式的具体实现方法。假设我们正在开发一个多线程下载器程序,该程序可以同时下载多个文件,并且可以监控每个文件的下载进度,当某个文件下载完成后,该程序需要自动关闭该文件的下载线程并向用户发送下载完成的提示信息。

为了实现上述功能,我们可以使用状态模式对下载器程序进行重构,具体设计如下:

  1. 定义抽象状态类,并声明抽象的 download 方法,用于封装不同状态下的共性操作。
  2. 定义具体状态类,并实现 download 方法,用于完成具体的状态操作逻辑。
  3. 在 ConcreteState 类中,定义一个静态变量来表示当前状态,在 download 方法中根据下载状态进行状态转换。
  4. 在 Context 类中,维护一个当前状态,并将 download 方法委托给当前状态对象来执行。

接下来我们来看一下示例程序的具体实现。在本示例程序中,我们使用了 Java 中的线程池和 FutureTask,实现了对多个文件的同时下载。需要注意的是,由于本文篇幅较长,为了让代码更加清晰,我们将代码拆分成了多个类来实现相应的功能。

(1)抽象状态类

public abstract class DownloadState {
    protected DownloadContext context;

    public void setContext(DownloadContext context) {
        this.context = context;
    }

    public abstract void download(String url, String filePath);
}

在上述代码中,我们定义了一个抽象状态类 DownloadState,它包含了一个 DownloadContext 对象,以及一个 download 方法,用于封装不同状态下的下载操作。需要注意的是,该抽象方法不包含具体的下载逻辑,具体的下载逻辑需要在具体状态类中进行实现。

(2)具体状态类

public class DownloadingState extends DownloadState {
    private FutureTask<Integer> futureTask;

    @Override
    public void download(String url, String filePath) {
        System.out.println("开始下载文件:" + filePath);

        // 开始下载
        DownloadTask task = new DownloadTask(url, filePath);
        futureTask = new FutureTask<>(task);
        ThreadPool.getInstance().execute(futureTask);

        // 状态转换
        try {
            int result = futureTask.get();
            if (result == 0) {
                context.setState(new FinishedState());
            } else {
                context.setState(new ErrorState());
            }
        } catch (Exception e) {
            e.printStackTrace();
            context.setState(new ErrorState());
        }
    }
}

public class FinishedState extends DownloadState {
    @Override
    public void download(String url, String filePath) {
        System.out.println("文件已下载完成,无需重复下载!");
        context.closeDownloadThread(filePath);
    }
}

public class ErrorState extends DownloadState {
    @Override
    public void download(String url, String filePath) {
        System.out.println("下载文件出错,无法继续下载!");
        context.closeDownloadThread(filePath);
    }
}

在上述代码中,我们定义了三个具体状态类:DownloadingState、FinishedState 和 ErrorState,它们分别代表下载中、下载完成和下载出错三种状态。其中,我们使用了线程池和 FutureTask 来实现下载操作,并根据下载结果进行状态转换(对于下载成功的情况,我们转换到FinishedState;对于下载出错的情况,我们转换到ErrorState)。

需要注意的是,由于下载完成或下载出错后都需要关闭下载线程,因此我们在FinishedState和ErrorState中都调用了context对象的closeDownloadThread方法来实现该功能。

(3)环境类

public class DownloadContext {
    private DownloadState currentState;
    private Map<String, FutureTask<Integer>> taskMap;

    public DownloadContext() {
        this.currentState = new FinishedState();
        this.taskMap = new HashMap<>();
      }

      public void setState(DownloadState state) {
        this.currentState = state;
        this.currentState.setContext(this);
      }

      public void download(String url, String filePath) {
        FutureTask<Integer> task = this.taskMap.get(filePath);

        if (task == null || task.isDone() || task.isCancelled()) {
          this.taskMap.remove(filePath);
          this.currentState.download(url, filePath);
        } else {
          System.out.println("文件 " + filePath + " 正在下载中,无需重复下载!");
        }
      }

      public void closeDownloadThread(String filePath) {
        FutureTask<Integer> task = this.taskMap.get(filePath);

        if (task != null) {
          task.cancel(true);
          this.taskMap.remove(filePath);
          System.out.println("已关闭文件 " + filePath + " 的下载线程。");
        }
      }
}

在上述代码中,我们定义了一个 DownloadContext 类,它包含了当前状态以及 download 和 closeDownloadThread 方法。

在 download 方法中,我们首先检查是否存在正在下载的任务(即 task 对象是否存在且未完成),如果不存在,则将当前状态转换为下载中状态,并启动下载任务;否则输出提示信息,防止重复下载。

在 closeDownloadThread 方法中,我们将传入的 filePath 对应的下载任务取消,并从 taskMap 中移除该任务,同时输出提示信息。

(4)线程池类

public class ThreadPool {
    private ExecutorService executor;

    private ThreadPool() {
        this.executor = Executors.newFixedThreadPool(5);
    }

    private static class Singleton {
        private static final ThreadPool INSTANCE = new ThreadPool();
    }

    public static ThreadPool getInstance() {
        return Singleton.INSTANCE;
    }

    public void execute(Runnable task) {
        this.executor.execute(task);
    }
}

在上述代码中,我们定义了一个 ThreadPool 类,它包含了一个静态的 ExecutorService 对象 executor,并封装了一个 execute 方法用于提交任务到线程池中。

需要注意的是,由于我们要将ThreadPool类设计为单例模式,因此我们在该类中定义了一个私有的静态内部类Singleton,用于实现懒汉式单例模式。这样可以保证线程池中只有一个实例对象,并且线程安全。

(5)示例程序

public class Main {
    public static void main(String[] args) {
        DownloadContext context = new DownloadContext();

        String url1 = "https://cdn.pixabay.com/photo/2018/10/30/16/06/water-lily-3784022__340.jpg";
        String filePath1 = "water-lily.jpg";

        String url2 = "https://cdn.pixabay.com/photo/2020/07/14/13/10/excursion-5407227__340.jpg";
        String filePath2 = "excursion.jpg";

        context.download(url1, filePath1);
        context.download(url2, filePath2);

        System.out.println("------------------------------------");

        context.download(url1, filePath1);
        context.download(url2, filePath2);
    }
}

在上述代码中,我们创建了一个 DownloadContext 对象,并分别下载了两个文件。需要注意的是,在第二次下载同一个文件时,系统会输出提示信息“文件正在下载中,无需重复下载!”。

运行该程序,我们可以看到如下输出结果:

开始下载文件:water-lily.jpg
开始下载文件:excursion.jpg
------------------------------------
文件正在下载中,无需重复下载!
文件正在下载中,无需重复下载!

从输出结果中我们可以看出,根据不同的状态,下载器程序完成了不同的操作,并且顺利地将多线程下载操作与状态转换功能封装在了不同的状态类中。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
本书浅显易懂的介绍了JAVA线程相关的设计模式,通过程序范例和UML图示来一一解说,书中代码的重要部分加了标注以使读者更加容易理解,再加上图文并茂,对于初学者还是程序设计高手来说,这都是一本学习和认识JAVA设计模式的一本好书。(注意,本资源附带书中源代码可供参考) 多线程与并发处理是程序设计好坏优劣的重要课题,本书通过浅显易懂的文字与实例来介绍Java线程相关的设计模式概念,并且通过实际的Java程序范例和 UML图示来一一解说,书中在代码的重要部分加上标注使读者更加容易解读,再配合众多的说明图解,无论对于初学者还是程序设计高手来说,这都是一本学习和认识设计模式非常难得的好书。 书中包含Java线程的介绍导读、12个重要的线程设计模式和全书总结以及丰富的附录内容。第一章相关线程设计模式的介绍,都举一反三使读者学习更有效。最后附上练习问题,让读者可以温故而知新,能快速地吸收书中的精华,书中最后附上练习问题解答,方便读者学习验证。 目录 漫谈UML UML 类图 类和层次结构的关系 接口与实现 聚合 访问控制 类间的关联性 顺序图 处理流程和对象间的协调 时序图 Introduction 1 Java语言的线程 Java语言的线程 何谓线程 明为追踪处理流程,实则追踪线程 单线程程序 多线程程序 Thread类的run方法和start方法 线程的启动 线程的启动(1)——利用Thread类的子类 线程的启动(2)——利用Runnable接口 线程的暂时停止 线程的共享互斥 synchronized方法 synchronized阻挡 线程的协调 wait set——线程的休息室 wait方法——把线程放入wait set notify方法——从wait set拿出线程 notifyAll方法——从wait set拿出所有线程 wait、notify、notifyAll是Object类的方法 线程的状态移转 跟线程有关的其他话题 重点回顾 练习问题 Introduction 2 多线程程序的评量标准 多线程程序的评量标准 安全性——不损坏对象 生存性——进行必要的处理 复用性——可再利用类 性能——能快速、大量进行处理 评量标准的总结 重点回顾 练习问题 第1章 Single Threaded Execution——能通过这座桥的,只有一个人 第2章 Immutable——想破坏它也没办法 第3章 Guarded Suspension——要等到我准备好喔 第4章 Balking——不需要的话,就算了吧 第5章 Producer-Consumer——我来做,你来用 第6章 Read-Write Lock——大家想看就看吧,不过看的时候不能写喔 第7章 read-Per-Message——这个工作交给你了 第8章 Worker Thread——等到工作来,来了就工作 第9章 Future——先给您这张提货单 第10章 Two-Phase Termination——快把玩具收拾好,去睡觉吧 第11章 Thread-Specific Storage——每个线程的保管箱 第12章 Active Object——接受异步消息的主动对象 总结 多线程程序设计的模式语言 附录A 练习问题的解答 附录B Java的内存模型 附录C Java线程的优先级 附录D 线程相关的主要API 附录E 参考文献
Java.Bug模式详解 第1章 混乱环境下的灵活方法 1.1 软件设计、实现和维护的趋势 1.1.1 对于稳定、安全 系统的需求增加 1.1.2 传统软件工程技 术的局限性 1.1.3 开放源代码的软 件项目的可利用性 1.1.4 对于跨平台语言 的需求 1.2 在快节奏的社会中学习 1.3 bug模式简述 1.4 小结 第2章 Bug、规范和实现方案 2.1 bug的概念 2.2 一体性规范 2.2.1 C++ 2.2.2 Python 2.2.3 ML 2.2.4 Pascal 2.3 规范的好处 2.4 实现方案与规范的差异 2.5 利用素材建立经济有效的规范 2.5.1 通过测试来排除 规范错误 2.5.2 单元测试的缺陷 2.6 小结 第3章 调试和开发过程 3.1 将调试当作科学试验 3.1.1 逐步规范化、整 合并发行软件 3.1.2 在设计上尽可能 保持简单 3.1.3 结对编程 3.1.4 及时的客户反馈 3.1.5 所有开发人员共 享程序代码 3.1.6 对任何可能产生 问题的代码进行测试 3.2 将调试测试程序并入到单元测 试集 3.3 展望:面向测试的语言 3.4 小结 第4章 调试和测试过程 4.1 可测试的设计模式 4.1.1 在模型中而不是 视图中保管代码 4.1.2 使用静态类型检 查发现错误 4.1.3 使用中介器封装 跨越断层线的功能 4.1.4 编写带有简短签 名和默认参数的方法 4.1.5 使用不修改内存 状态的存取器 4.1.6 通过接口定义程 序外组件 4.1.7 优先编写测试程 序 4.2 GlobalModel接口 4.3 小结 第5章 科学的调试方法 5.1 软件是永不磨损的机器 5.1.1 软件有多重 5.1.2 小异常引起大问 题 5.2 Bug模式可以加快诊断bug的速度 5.3 小结 第6章 关于bug模式 6.1 了解bug模式的重要性 6.2 选择bug模式的原因 6.3 如何组织bug模式 6.4 Bug诊断的快速参考 第7章 Rogue Tile模式 7.1 Rogue Tile bug模式简述 7.1.1 症状 7.1.2 起因、解决方法 和预防措施 7.2 提取代码的其他障碍 7.2.1 通用类型 7.2.2 面向方面的编程 技术 7.3 小结 第8章 随处可见的空指针 8.1 空指针异常不提供任何信息 8.2 难以捉摸的空指针 第9章 Dangling Composite模式 9.1 Dangling Comp osite bug模式简述 9.1.1 症状 9.1.2 起因 9.1.3 解决方法和预防 措施 9.2 小结 第10章 Null Flag模式 10.1 Null Flag bug模式简述 10.1.1 症状 10.1.2 起因 10.1.3 解决方法和预 防措施 10.2 健壮性和诊断证据的缺乏 10.2.1 在更好的位置 处理异常 10.2.2 处理老式代码 10.3 小结 第11章 Double Descent模式 11.1 Double Descent bug模式简述 11.1.1 症状 11.1.2 起因 11.1.3 解决方法和预 防措施 11.1.4 快速但不完善 的修正方法 11.1.5 真正的修正方 法 11.2 小结 第12章 Liar View模式 12.1 Liar View bu g模式简述 12.1.1 症状 12.1.2 起因 12.1.3 解决方法和预 防措施 12.2 Liars并非仅出现在GUI程序 12.3 小结 第13章 Saboteur Data模式 13.1 Saboteur Data bug模式简述 13.1.1 症状 13.1.2 语法原因 13.1.3 语义原因 13.1.4 解决办法和预 防措施 13.2 小结 第14章 Broken Dispatch模 式 14.1 Broken Dispatch bug简述 14.1.1 症状 14.1.2 起因 14.1.3 解决方法和预 防措施 14.2 小结 第15章 Impostor Type模式 15.1 Impostor Type bug模式简述 15.1.1 症状 15.1.2 起因 15.1.3 解决方法和预 防措施 15.2 混合模式 15.3 小结 第16章 Split Cleaner模式 16.1 Split Cleaner bug模式简述 16.1.1 症状 16.1.2 起因 16.1.3 解决方法和预 防措施 16.2 小结 第17章 Fictitious Implementation模式 17.1 Fictitious Implementation bug模式简述 17.1.1 症状 17.1.2 起因 17.1.3 检测Fict.. itious Implementation 17.1.4 解决方法和预 防措施 17.2 小结 第18章 Orphaned Thread模 式 18.1 Orphaned Thread bug模式简述 18.1.1 症状 18.1.2 起因 18.1.3 解决方法和预 防措施 18.2 Orphaned Thread和GUI 18.3 小结 第19章 Run-on Initializatier模式 19.1 Run-on Initializatier bug模式简述 19.1.1 症状和起因 19.1.2 解决方法和预 防措施 19.2 修正bug 19.3 小结 第20章 Platform-Dependent模式 20.1 Platform-Dependent bug模式简述 20.1.1 与供应商相关 的bug 20.1.2 与版本相关的.. bug 20.1.3 与操作系统相 关的bug 20.2 小结 第21章 诊断清单 21.1 基本概念 21.2 模式清单 第22章 用于调试的设计模式 22.1 最大化静态类型检查 22.1.1 尽可能设置final字段 22.1.2 将不可能被改 写的方法设为final 22.1.3 包括作为默认 值的类 22.1.4 利用已检查异 常确保所有客户端程序可处理异常情况 22.1.5 定义新的异常 类型来精确区分各种异常情况 22.1.6 利用特定State类 22.1.7 将类型转换和 instanceof测试降至最少 22.1.8 使用Singleton设计模式帮助最小化instanceof的使用 22.2 将引入bug的可能降至最 低 22.2.1 提取通用代码 22.2.2 尽可能实现纯 功能性方法 22.2.3 在构造函数中 初始化所有字段 22.2.4 出现异常情况 时立即抛出异常 22.2.5 出现错误时立 刻报告错误消息 22.2.6 尽早发现错误 22.2.7 在代码中置入 断言 22.2.8 尽可能在用户 可观察到的状态下测试代码 22.3 征程尚未结束 第23章 参考资料 附录 String-parsing列表构造 函数 术语表 附录页
很抱歉,根据提供的引用内容中没有包含java享元模式的UML图。但是可以为您简要介绍java享元模式的UML图。 享元模式的UML图如下所示: - Flyweight:享元接口,定义了享元对象的公共方法。 - ConcreteFlyweight:具体享元对象,实现了享元接口,并存储了内部状态。 - UnsharedConcreteFlyweight:非共享具体享元对象,不共享的对象,一般不会出现在享元工厂中。 - FlyweightFactory:享元工厂类,负责创建和管理享元对象。 - Client:客户端类,通过享元工厂获取享元对象,并操作具体享元对象。 请注意,由于无法直接提供UML图,以上是一份简化的描述。在实际应用中,可能还会有其他的类和关系。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Java设计模式之享元模式(UML类图分析+代码详解)](https://blog.csdn.net/m0_51750419/article/details/127332662)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [设计模式-享元模式](https://blog.csdn.net/qq_44065088/article/details/108725714)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大家都说我身材好

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值