如何优雅地实现撤销和回退功能
创作时间:
作者:
@小白创作中心
如何优雅地实现撤销和回退功能
引用
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实现
热门推荐
Ubuntu构建只读文件系统
静脉用药调配中心:守护患者用药安全的幕后英雄
香港中银账户管理费:了解费用标准及如何降低成本
家里漏水但是不知道哪里漏怎么办?快速排查与解决方案
如何提高网球运动员心理素质和比赛心态?
儿童吃什么鱼最好最有营养的方法
沙特状态不佳但实力仍在国足之上,平局中国队也能接受
法定继承人的资格如何认定
《周易》卜卦有哪些方法?
股票孕线的特征是什么?股票孕线的出现意味着什么?
校园处处是书香!让阅读成为学校闪亮的文化符号,为学生成长铺就精神底色
女生一定要试试这种新型约会方式,真的快乐疯了
因果推荐|可解释推荐系统的反事实语言推理
罗成韩信为何英年早逝?民间传说揭秘折寿背后故事
潘金莲为何选择武大郎?从经济到情感的多重解读
莫言生活励志名言:20句金句激励你追逐梦想
如何查询二手车维保记录?5个渠道教你查明维修历史,简单易操作
手脚麻木可能是什么病的预兆?按摩、中药泡洗管用吗?
一文读懂三国时期荆州到底有多大?为何丢了荆州,蜀汉就没戏了?
美国分布式能源发展对我国的启示
ECE R113法规解读:提升摩托车前照灯安全性能
工地管理人员考勤打卡刷脸系统的准确率如何?
怎样查自己被法院传票了
2025心理学考研知识梳理:皮亚杰的认知发展阶段
熟记这些经典药对配伍口诀,让药效“如虎添翼”
HPMA的溶解性与溶解速率详解
鲤鱼焙面:河南传统名菜中的美味与文化的完美结合
赛博朋克是什么(赛博朋克概念解析及特点)
WLK怀旧服幻化详细规则 蛋刀已确认可以幻化
嗑瓜子讲究多!遇到这种情况千万要注意