如何优雅地实现撤销和回退功能
创作时间:
作者:
@小白创作中心
如何优雅地实现撤销和回退功能
引用
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实现
热门推荐
最近很多福州人中招!预计1月中旬到达峰值,还出现了新症状?
口腔正畸安氏分类标准详情解析:一、二、三类错颌畸形的临床表现与治疗建议
撞出租车误工费由谁承担
我国“八纵八横”高铁网建设进展及未来规划
特殊工种的工龄怎么折算
舌尖上的长沙:十大特色美食
中山市博物馆:一座活化的城市记忆殿堂
【辨识中药】柴胡也要 分“南”“北”,选用应当细辨证
如何撤销澳洲留学签证申请书
《说文解字》| 什么叫“文”,什么叫“字”?
唐山地震遗址:弘扬抗震精神 讲好唐山故事
木桶原理下的企业管理策略与实践案例
羚羊粉的功效与临床应用
H20 GPU算力评估分析
线路板丝印上的信息及其重要作用
柳宗元与刘禹锡:莫逆之交,情深似海
10本心灵治愈的书籍
民间借贷案件诉讼费用由谁承担
什么是禅宗?禅宗为何与阳明心学一脉相承?
乳果糖口服溶液的作用与使用注意事项
如果用太阳系其它行星代替月球会怎样?木星最壮观,土星最美丽
氧气纯度等级及其应用
去外地骑行如何打包自行车?给你2种方式!
3分!300元!这样开车会被罚!
接地线的正确方法和标准是什么
公司各种税收有哪些,分别怎么收取
生普和熟普是什么意思区别:详解普洱茶生茶与熟茶的差异
人工智能如何拍摄的
无障碍成为城市美丽的风景——写在无障碍环境建设法实施一周年之际
住宅新风系统电线电缆选择指南