命令设计模式实现行为+数据的撤销undo/恢复redo功能

前言

公司最近有个需求就是要解析用户的画图行为动作,客户端记录保存了用户的行为+数据,然后将各端实现不同的需求,比如用户的画图行为回放,还要实现比如常用编辑器的撤销恢复等功能,在此可以使用命令设计模式实现,看了网上很多文章千篇一律,也只是实现了简单的undo、redo功能,所以也请教同事给了一份写好的客户端算法代码作为参考,然后融合到自己的后端代码中。

实现原理

大概实现原理:将每次执行过的命令和数据保存到undo回退队列中,当执行undo操作时候取出队列数据进行执行即可,若是添加操作,undo时则执行删除操作,若是删除操作,undo时则实行添加操作,每次执行完后放入redo队列中,用于下一次的redo。
思维导图撤销恢复

由于公司代码不允许外传,所以自己模拟写了一个简单的说话后悔药的功能分享给大家,看懂代码理论上适用于95%以上的撤销恢复场景。

后悔药DEMO案例实现功能:允许你随便说,记录过程+行为,然后只呈现你最终要说出去的话。

代码实现

SomeThingDomain.java:任意数据和行为实体,可以根据自己业务扩展,这里做demo只是呈现保存一句话desc;

/**
 * SomeThingDomain
 * 任意数据和行为
 * @author lcry
 */
@Data
@Builder
public class SomeThingDomain implements Serializable, Cloneable {
    /**
     * 操作类型
     */
    private OperateTypeEnum operateType;
    /**
     * 索引
     */
    private List<Integer> index;
    /**
     * 说的一句话
     */
    private String desc;



    /**
     * 克隆对象
     */
    @Override
    public SomeThingDomain clone() {
        try {
            return (SomeThingDomain) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

OperateTypeEnum.java:操作枚举,可以自己添加更多

/**
 * OperateTypeEnum
 * 操作类型枚举
 *
 * @author lcry
 */
public enum OperateTypeEnum {
    /**
     * 未知
     */
    UNKNOWN(0),
    /**
     * 说一句话
     */
    ADD(1),
    /**
     * 删除随机几段话
     */
    DEL(2),
    /**
     * 清空所有说话
     */
    CLEAR(3),
    /**
     * 撤销说的一些话
     */
    UNDO(4),
    /**
     * 恢复说过的话
     */
    REDO(5);

    OperateTypeEnum(Integer type) {

    }
}

Action.class:动作接口

/**
 * Action
 * 动作接口
 *
 * @author lcry
 */
public interface Action {
    /**
     * 执行命令,可以是增加、删除、清空、剪切、粘贴等操作
     */
    void exec();

    /**
     * 撤销
     */
    void undo();

    /**
     * 恢复
     */
    void redo();
}

AddAction.java:添加操作实现类

/**
 * AddAction
 * 添加操作
 * 添加一句话或者多句话
 * @author lcry
 */
public class AddAction implements Action {
    /**
     * 最终要说出的话
     */
    private final List<SomeThingDomain> result;
    /**
     * 缓存添加的那一句话
     */
    private final List<SomeThingDomain> temp = new ArrayList<>();
    /**
     * 当前要添加的那句话
     */
    private final SomeThingDomain addSomeThingDomain;

    public AddAction(List<SomeThingDomain> result, SomeThingDomain addSomeThingDomain) {
        this.result = result;
        this.addSomeThingDomain = addSomeThingDomain;
    }

    @Override
    public void exec() {
//       添加一句话
        result.add(addSomeThingDomain);
    }

    @Override
    public void undo() {
//        撤销添加的那一句话
        result.remove(addSomeThingDomain);
//        清空缓存
        temp.clear();
//        把撤销的那句话添加到缓存中,方便下一次在redo
        temp.add(addSomeThingDomain);
    }

    @Override
    public void redo() {
//        恢复撤销的那一句话
        result.add(addSomeThingDomain);
//        清空缓存
        temp.clear();
//        把恢复的那句话添加到缓存中,方便下一次再undo
        temp.add(addSomeThingDomain);
    }
}

CleanAction.java、DeleteAction.java和AddAction.java代码差不多,只是逻辑不一样,篇幅有限参考文章前面的思路编写即可。

OperateManager.java:操作管理类

/**
 * OperateManager
 * 操作管理
 *
 * @author lcry
 */
public class OperateManager {
    /**
     * 最终要说出的话
     */
    private final List<SomeThingDomain> result = new ArrayList<>();
    /**
     * 动作管理,保存每次操作用于undo、redo
     */
    private final ActionManager actionManager = new ActionManager();

    /**
     * 解析说话过程->最终要表达的话
     *
     * @param paths 每次说出的一句话,行为+数据
     * @return 显示的最终笔迹
     */
    public List<SomeThingDomain> getResults(List<SomeThingDomain> paths) {
        result.clear();
        for (SomeThingDomain op : paths) {
            Action action = null;
            switch (op.getOperateType()) {
                case ADD:
                    System.out.println("执行添加操作" + print());
                    action = new AddAction(result, op.clone());
                    break;
                case DEL:
                    System.out.println("执行删除操作" + print());
                    action = new DeleteAction(result, op.getIndex());
                    break;
                case CLEAR:
                    System.out.println("执行删除操作" + print());
                    action = new CleanAction(result);
                    break;
                case UNDO:
                    System.out.println("执行撤销操作" + print());
                    actionManager.undo().undo();
                    break;
                case REDO:
                    System.out.println("执行恢复操作" + print());
                    actionManager.redo().redo();
                    break;
                default:
                    break;
            }
            if (action != null) {
//                命令模式:封装成对象去执行操作,而不是直接去执行操作
                action.exec();
                actionManager.setLastAction(action);
            }
        }
        // 清空之前的undo redo 操作队列
        actionManager.clear();
        return result;
    }

    /**
     * 打印每个步骤的结果
     */
    private String print() {
        StringBuffer resultTalk = new StringBuffer("   -> 结果:");
        result.forEach(a -> resultTalk.append(a.getDesc()));
        return resultTalk.toString();
    }
}

测试用例代码:

/**
 * TestSomeThing
 * 测试用例
 *
 * @author lcry
 */
public class TestSomeThing {

    /**
     * 测试整个说话流程
     *
     * @param args
     */
    public static void main(String[] args) {
        OperateManager operateManager = new OperateManager();
        List<SomeThingDomain> results = operateManager.getResults(getDemoSomeThing());
        StringBuffer resultTalk = new StringBuffer();
        results.forEach(a -> resultTalk.append(a.getDesc()));
        System.out.println("最终要说出的话为:" + resultTalk);
    }


    /**
     * 模拟数据
     *
     * @return 模拟完整的一个说话流程
     */
    private static List<SomeThingDomain> getDemoSomeThing() {
//        结果:第一句话|
        SomeThingDomain oneAdd = SomeThingDomain.builder().operateType(OperateTypeEnum.ADD).index(Collections.singletonList(0)).desc("第一句话|").build();
//       结果: 第一句话|第二句话|
        SomeThingDomain twoAdd = SomeThingDomain.builder().operateType(OperateTypeEnum.ADD).index(Collections.singletonList(1)).desc("第二句话|").build();
//       结果: 第一句话| 第二句话| 第三句话|
        SomeThingDomain threeAdd = SomeThingDomain.builder().operateType(OperateTypeEnum.ADD).index(Collections.singletonList(2)).desc("第三句话|").build();
//       结果: 第一句话| 第二句话| 第三句话| 第四句话|
        SomeThingDomain fourAdd = SomeThingDomain.builder().operateType(OperateTypeEnum.ADD).index(Collections.singletonList(3)).desc("第四句话|").build();
//       结果: 第一句话| 第二句话| 第三句话| 第四句话| 第五句话|
        SomeThingDomain fivesAdd = SomeThingDomain.builder().operateType(OperateTypeEnum.ADD).index(Collections.singletonList(4)).desc("第五句话|").build();
//       结果:  第四句话| 第五句话
        SomeThingDomain delete = SomeThingDomain.builder().operateType(OperateTypeEnum.DEL).index(Arrays.asList(0, 1, 2)).build();

        SomeThingDomain undo = SomeThingDomain.builder().operateType(OperateTypeEnum.UNDO).build();
        SomeThingDomain redo = SomeThingDomain.builder().operateType(OperateTypeEnum.REDO).build();
        SomeThingDomain clear = SomeThingDomain.builder().operateType(OperateTypeEnum.CLEAR).build();

        return Arrays.asList(oneAdd, twoAdd, threeAdd, fourAdd, fivesAdd, delete, undo, redo, clear, undo);

    }
}

执行结果:

....
执行添加操作   -> 结果:
执行添加操作   -> 结果:第一句话|
执行添加操作   -> 结果:第一句话|第二句话|
执行添加操作   -> 结果:第一句话|第二句话|第三句话|
执行添加操作   -> 结果:第一句话|第二句话|第三句话|第四句话|
执行删除操作   -> 结果:第一句话|第二句话|第三句话|第四句话|第五句话|
执行撤销操作   -> 结果:第四句话|第五句话|
执行恢复操作   -> 结果:第一句话|第二句话|第三句话|第四句话|第五句话|
执行删除操作   -> 结果:第四句话|第五句话|
执行撤销操作   -> 结果:
最终要说出的话为:第四句话|第五句话|

源码下载

https://download.csdn.net/download/qq_19244927/15578692

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值