读美团技术博客《设计模式二三事》的一些想法

289 阅读2分钟

任务模型的设计

任务模型的设计 这段,我觉得 TaskState 应该是一个不可变的值对象,它不应该有过多的创建 new,枚举+不同实现的方法我觉得是个不错的选择。另外,TaskState 应该独立于 Task 对象,它的方法参数中不依赖 Task 参数。

这是我改进的 TaskState 类:

/**
 * 任务状态枚举
 */
@AllArgsConstructor
@Getter
public enum TaskState {

    INIT("初始化", TaskState::dispatchOnInit),
    ONGOING("进行中", TaskState::dispatchOnOngoing),
    PAUSED("暂停中", TaskState::dispatchOnPaused),
    FINISHED("已完成", TaskState::dispatchOnFinished),
    EXPIRED("已过期", TaskState::dispatchOnExpired);

    private final String message;
    private final Function<ActionType, TaskState> reducer;

    public TaskState dispatch(ActionType actionType) {
        return reducer.apply(actionType);
    }


    // 这里直接使用方法引用的方式在class里直接实现逻辑,当然也可以包装到新 Function 实现类里
    private static TaskState dispatchOnInit(ActionType actionType) {
        switch (actionType) {
            case START:
                return ONGOING;
            default:
                return INIT;
        }
    }

    private static TaskState dispatchOnOngoing(ActionType actionType) {
        switch (actionType) {
            case ACHIEVE:
                return FINISHED;
            case STOP:
                return PAUSED;
            case EXPIRE:
                return EXPIRED;
            default:
                return ONGOING;
        }
    }

    private static TaskState dispatchOnPaused(ActionType actionType) {
        switch (actionType) {
            case START:
                return ONGOING;
            case EXPIRE:
                return EXPIRED;
            default:
                return PAUSED;
        }
    }

    private static TaskState dispatchOnFinished(ActionType actionType) {
        return FINISHED;
    }
    
    private static TaskState dispatchOnExpired(ActionType actionType) {
        return EXPIRED;
    }


}

/**
 * 行为枚举
 */
@AllArgsConstructor
@Getter
enum ActionType {
    START(1, "开始"),
    STOP(2, "暂停"),
    ACHIEVE(3, "完成"),
    EXPIRE(4, "过期");

    private final int code;
    private final String message;
}
  • 每个 TaskState 实例有自己接受不同 actionType 时的行为,即 reducer 字段
  • dispatchOn* 方法为 TaskState 接受不同 actionType 时的行为具体实现
  • TaskState 不可变,每次 dispatch 都返回新的 TaskState
  • dispatchOn* 可以定义为方法来编写,也可以独立出来作为 Function 实现类来编写

使用发布-订阅模式来代替发布-订阅模式进行解耦,这里使用 Vert.X 的 EventBus 作为发布订阅管道:

public class Task {
    
    private final Long taskId;

    private TaskState state = TaskState.INIT;

    private final EventBus eventBus;

    public void updateState(ActionType actionType) {

        TaskState startState = state;
        state = state.dispatch(actionType);
        
        // 发布事件
        // 1. 当状态变为 FINISHED 时发送事件
        if ( startState != TaskState.FINISHED && state == TaskState.FINISHED) {
            eventBus.publish(TaskState.FINISHED.name(), taskId);
        }
    }
}

订阅事件,调用活动服务和任务管理器

    eventBus.<Long>consumer(TaskState.FINISHED.name(), msg -> {
        Long taskId = msg.body();
        // 模拟调用活动服务
        System.out.println("活动服务: " + taskId + " 完成");
        // 模拟调用任务管理器
        System.out.println("任务管理器: " + taskId + " 完成");
    });

写段测试代码,测试下是否可用

    public static void main(String[] args) {
        EventBus eventBus = Vertx.vertx().eventBus();

        // 订阅事件
        eventBus.<Long>consumer(TaskState.FINISHED.name(), msg -> {
            Long taskId = msg.body();
            // 模拟调用活动服务
            System.out.println("活动服务: " + taskId + " 完成");
            // 模拟调用任务管理器
            System.out.println("任务管理器: " + taskId + " 完成");
        });
        
        // 测试
        Task task = new Task(1002L, eventBus);
        
        print(task, ActionType.START);
        print(task, ActionType.STOP);
        print(task, ActionType.START);
        print(task, ActionType.ACHIEVE);
        print(task, ActionType.EXPIRE);

        task = new Task(1003L, eventBus);
        print(task, ActionType.STOP);
        print(task, ActionType.START);
        print(task, ActionType.EXPIRE);
        print(task, ActionType.START);
        print(task, ActionType.ACHIEVE);
    }

    static void print(Task task, ActionType actionType) {

        TaskState startState = task.getState();
        task.updateState(actionType);
        TaskState endState = task.getState();

        System.out.printf("Task[%s]: %s + %s -> %s \n", task.getTaskId(), startState, actionType, endState );
    }

控制台输出:

image.png

因为订阅服务的执行是异步的,所以控制台输出顺序不是串行的

可见,任务的状态变化订阅服务的执行是符合预期的。