如何优雅地实现撤销和回退功能
创作时间:
作者:
@小白创作中心
如何优雅地实现撤销和回退功能
引用
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实现
热门推荐
佛山四季划分
穿越时空的爱情考验:《时间旅行者的妻子》爱情哲学解析
四大底色看贵州白酒 | 见证2024
膝蓋疼痛?中醫療法治療膝關節炎新趨勢
电子港澳通行证刷脸:法律合规与隐私保护
八字命理学:探索生辰奥秘
中国男篮官宣26人大名单,辽宁一人广东四人,赵继伟赵睿齐麟缺席
古城保护复兴“姑苏模式”:一条平江路,织就传统与现代的双面绣
奥尔夫教学法与音乐治疗
新手养猫必看!鱼油到底是神药还是智商税?揭秘猫咪鱼油的真相!
纳斯达克100股指期货实时趋势展望:影响因素分析和预测
新研究:长期单身的人分4个类型,有一种其实很幸福
北京地铁1号线:探索京城的多样风光与丰富文化!
探秘宝可梦:基拉祈的奇幻之旅与魅力解析
DeepSeek热潮下,AI加速重构搜索新范式
工时长、压力大,医护人员的职场幸福解方是什么?
十二生肖的文化精髓及其深远影响
《星际争霸:重制版》三大种族背景及战术解析
从治愈到争议:基因编辑技术的力量与陷阱
新入校的学生都要做结核筛查,做结核菌素实验是怎么回事?
怀孕后胃胀气是怎么回事
中国古建筑怎么分类?
股票分红的钱去哪里了,股价为什么降低了
不只有街机格斗!《天外魔境》全系列回顾
哪吒VS孙悟空,两者对决谁的胜算更大?让DeepSeek来分析一下
恋爱中的情感界限,情侣是否需要独立又彼此依赖的空间?
九上名著《水浒传》考点解读及作品介绍
暗恋之花(从情感到象征)
全面解析NFC技术及其在支付、安防和标签中的应用
一双职业运动员禁用的跑鞋,背后是中国跑步科技的坚持