如何优雅地实现撤销和回退功能
创作时间:
作者:
@小白创作中心
如何优雅地实现撤销和回退功能
引用
CSDN
1.
https://blog.csdn.net/minminaya/article/details/123678029
本文将介绍如何使用环形双向链表实现撤销和重做功能。通过分析需求,设计数据结构,并提供完整的代码实现,帮助开发者快速实现这一功能。
一、需求分析
需求是实现一个可以撤销和重做的功能,具体包括:
- 添加数据
- 向左撤销数据
- 向右重做数据
- 控制向左撤销的最大数量
- 支持并发操作
二、设计思路
分析需求,我们需要的是主要是3个功能,添加数据、往左得到左边的数据、往右得到右边的数据,正常情况下,如果不要数字指针,也就说明不会有数字的增和减,那么只能用链表来实现。为了节省内存,可以考虑使用环形链表实现。并且可以用一个当前的节点的全局变量来代替数字的指针。
三、代码实现
1. 定义各个节点
// 头结点
private Node<T> mHead;
// 尾结点
private Node<T> mTail;
// 当前的显示的节点
private Node<T> mCurrentNode;
2. 往链表尾部插入节点
/**
* 在链表表尾插入一个结点
*
* @param data
*/
private void insertInTail(T data) {
Node<T> newNode = new Node<>(data);
// 保存为当前的节点
this.mCurrentNode = newNode;
if (mTail == null) {
// 为null,说明是尾节点
mHead = newNode;
mTail = newNode;
// 和头部相连接,形成环形双向链表
mTail.mNext = mHead;
mHead.mPrevious = mTail;
} else {
newNode.mPrevious = mTail;
mTail.mNext = newNode;
mTail = newNode;
// 和头部相连接,形成环形双向链表
mTail.mNext = mHead;
mHead.mPrevious = mTail;
}
}
3. 判断左右边界
/**
* 是否是左边界
*
* @return false代表是左边界
*/
public boolean isLeftBound() {
return mCurrentNode == mHead || mCurrentNode == null;
}
4. 拿到向左向右的数据
private T getPreNode() {
if (mHead == null) {
return null;
}
if (isLeftBound()) {
// 如果是左边界
return mHead.mData;
}
mCurrentNode = mCurrentNode.mPrevious;
return mCurrentNode.mData;
}
5. 删除节点之后的数据
/**
* 删除链表指定结点之后的元素,具体做法是当前的Node直接连接头节点
*
* @param node
* @return
*/
private void deleteAfterNode(Node<T> node) {
if (node == null) {
return;
}
Node<T> cur = node.mNext;
while (cur != mHead) {
Node<T> dest = cur;
cur = cur.mNext;
dest.mNext = null;
dest.mPrevious = null;
}
mTail = node;
mTail.mNext = mHead;
mHead.mPrevious = mTail;
}
6. 将当前的头部节点前移
/**
* 当前的指针头部前移
*/
private void replaceCurrentHead() {
Node<T> node = mHead;
mHead = mHead.mNext;
node.mNext = null;
node.mPrevious = null;
mTail.mNext = mHead;
mHead.mPrevious = mTail;
}
7. 遍历链表得到所有节点的数量
/**
* 返回计算后的链表长度
*
* @return
*/
private int size() {
if (mTail == null) {
// 如果尾部没有值,那么size为0
return 0;
}
// 尾部有值的情况
int size = 1;
// 如果尾部有值,那么开始遍历每一个项
Node cur = mTail;
while (cur != mTail.mNext) {
size++;
cur = cur.mPrevious;
}
return size;
}
8. 封装put函数
public void put(T data) {
deleteAfterNode(mCurrentNode);
if (size() >= mCount) {
insertInTail(data);
// 当前的头部前移
replaceCurrentHead();
return;
}
// 执行插入
insertInTail(data);
}
9. 封装undo redo函数
/**
* 向左撤销
*
* @return
*/
public T undo() {
return getPreNode();
}
/**
* 向后恢复
*
* @return
*/
public T redo() {
return getNextNode();
}
10. 删除链表所有数据
/**
* 删除链表所有数据
*/
public void removeAll() {
if (mHead == null) {
return;
}
Node cur = mHead;
while (cur != mHead.mPrevious) {
Node dest = cur;
cur = cur.mNext;
dest.mNext = null;
dest.mPrevious = null;
}
mHead = null;
mTail = null;
mCurrentNode = null;
}
四、改造成同步链表
1. 当前显示的节点添加volatile关键字
// 当前的显示的节点
private volatile Node<T> mCurrentNode;
2. 改造put方法
public void put(T data) {
synchronized (UndoRedoLinkedList.this) {
deleteAfterNode(mCurrentNode);
if (size() >= mCount) {
insertInTail(data);
// 当前的头部前移
replaceCurrentHead();
return;
}
// 执行插入
insertInTail(data);
}
}
3. 改造undo、redo方法
/**
* 向左撤销
*
* @return
*/
public synchronized T undo() {
return getPreNode();
}
/**
* 向后恢复
*
* @return
*/
public synchronized T redo() {
return getNextNode();
}
4. 改造范型的数据结构
为了能完全的将数据类型,比如bitmap完全清除出内存,有时候需要在将Bitmap置空之前调用recycle方法,
A. 定义接口
public interface Entry {
void onDestroy();
}
B. 改造范型
public class UndoRedoLinkedList<T extends UndoRedoLinkedList.Entry> {
C. 数据结构添加volatile关键字
private static class Node<T> {
// 业务的数据
private T mData;
private volatile Node<T> mPrevious;
private volatile Node<T> mNext;
Node(T data) {
mData = data;
}
}
5. 将范型周期删除的回调填入各个方法。
比如
/**
* 删除链表所有数据
*/
public synchronized void removeAll() {
if (mHead == null) {
return;
}
Node<T> cur = mHead;
while (cur != mHead.mPrevious) {
Node<T> dest = cur;
cur = cur.mNext;
dest.mData.onDestroy();
dest.mNext = null;
dest.mPrevious = null;
}
mHead = null;
mTail = null;
mCurrentNode = null;
}
6. 给各个方法加上同步关键字synchronized
略
7. 范型需要传入的数据结构
public class UndoRedoBean implements UndoRedoLinkedList.Entry {
private String mData = null;
private int mIndex = 1;
public void setData(String data) {
mData = data;
}
public String getData() {
return mData;
}
public int getIndex() {
return mIndex;
}
@Override
public void onDestroy() {
mIndex = 0;
}
}
例子:UndoRedoDemo
更多
- Command模式实现撤销重做(Undo/Redo)
- 设计模式 - 命令模式(command pattern) 撤销(undo) 详解
- 数据结构之链表及其Java实现
热门推荐
项目经理如何升产品经理
欧联杯法兰克福vs阿贾克斯预测分析 法兰克福手握1球优势
2025,探索不止!看中国航天迭代创新延续精彩
有效沟通在解决冲突中的作用:如何化解矛盾,达成共识?
书籍推荐 | 管理精力 而非时间——《精力管理》
怎样判断骨骼有没有封闭?这些方法值得了解
利用智慧解决方案改善公共交通设施
通过掌握基本的制作步骤和要点,在家轻松制作出美味的菠菜鸡蛋羹
成都五行属金还是土
如何分析实物黄金每年价格的变化趋势?这种趋势对长期投资有何影响?
怎么培养和提升炒股的专业技能?这些技能在实际操作中的应用效果如何?
股票中什么叫散户:散户投资者行为特点与市场影响力探讨
应急仓库管理系统的智能化升级:AI技术在库存监控中的应用
签了合同违约怎么办?书面申请退款全攻略
一天之内两名脑梗患者二次复发!医生提醒:这三件事千万别做太多
王者模拟战中弃牌机制详解,理解弃牌规则以优化策略布局
四肢纤细个子高可能是一种病?13岁男孩手脚细长被确诊马凡综合征
期权贴水的含义是什么?如何利用贴水进行套利操作?
流水摆件风水宜忌,风水摆件流水的作用
旅游怎么做项目管理计划
晚餐吃土豆减肥吗?专家解读土豆减肥效果
剩饭剩菜太多怎么办?专家提醒:宁可剩荤不剩素
涡扇15列装,仅相当于美国30年前的技术?国产航发究竟经历了什么?
在校如何找产品经理实习
前瞻 | 如何用好生成式AI这一新质生产力?
2024年“十大流行语”发布,“数智化”“city不city”等入选
机器学习中的决定系数(R²)详解
Windows 11安装NVIDIA+CUDA(带cuDNN)
警示曝光:车辆未年检、无保险被处罚!
药补不如食补,对心脏特别好的5种食物,常吃气血通畅,免疫力强