java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java状态模式

Java设计模式之状态模式详解

作者:蜀山剑客李沐白

Java 中的状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态发生改变时改变其行为,本文将详细介绍 Java 中的状态模式,我们将从状态模式的概述、结构与实现、优缺点、适用场景等方面进行讲解,需要的朋友可以参考下

1. 状态模式的概述

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

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

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

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

下面是 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. 状态模式的适用场景

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

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

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

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

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

接下来我们来看一下示例程序的具体实现。在本示例程序中,我们使用了 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
------------------------------------
文件正在下载中,无需重复下载!
文件正在下载中,无需重复下载!

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

以上就是Java设计模式之状态模式详解的详细内容,更多关于Java状态模式的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文