备忘录模式实现画图板撤销操作

一、设计模式概念

设计模式给人的第一感觉,大概就是抽象,还有遥不可及。其实随着开发经验的逐渐积累,当你偶尔翻一下关于设计模式之类的书籍,你就会发现,里面的某些模式自己曾经实现过,只是尚未上升到理论的阶段。

设计模式,是针对某一类问题的最佳解决方案。可以这样定义设计模式:“设计模式是从许多优秀的软件系统中总结出来的可复用的设计方案。在这里,不打算讨论全部设计模式的定义和使用。只是希望抛砖引玉,通过一个图形界面,让读者对于设计模式之一的“备忘录模式”有一个直观的感受。

二、问题提出

在说明备忘录模式之前,先抛出一个问题。玩过单机游戏的读者,应该经常使用到游戏的保存功能。单机等游戏能够随时将当前的进度保存起来,以便在以后的某个时刻读取进度,这样的游戏可以满足玩家长时间的体验。那么,如何使用设计模式来优雅地解决这样的问题呢?希望看完全文后的读者能够对解决这个问题有个初步的感知。

一直以来,设计模式由于本身的抽象性和复杂性,容易让初学者望而却步。因此本方所用的例子,将抛开纯理论分析,以一个画图板的undo操作来说明问题。

先给出备忘录模式的定义——

三、备忘录模式

“备忘录模式,是在不破坏封闭性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将对象恢复到原先保存的状态”。该定义来自GOF四人帮对该模式的高度概括。

备忘录模式包括三种角色。

原发者——需要在某个时刻保存其状态的对象。原发者负责创建备忘录来保存自己的状态。当原发者需要恢复自身状态到某个时刻,它通过获得相应备忘录中的数据来还原。

备忘录——负责存储原发者状态的对象。

负责人——负责管理保存备忘录的对象,包括保存和获取备忘录对象。如果需要将备忘录对象持久化,负责人可以使用对象流将其写入文件。

四、案例现场

下面使用备忘录模式来设计一个GUI程序,主要功能如下:

程序通过一个画图板显示,当用户在画板上点击并且拖动后释放,就会在起点和终点之间形成一条直线。

程序提供清空操作,当用户点击鼠标中键的时候,画板全部内容被清空。

程序提供undo(撤销)操作,当用户点击鼠标右键的时候,最近一次操作被还原。

五、代码演示

原发者对象设计

本例中,原发者(画板)是JPanel的一个子类实例。该类主要包括创建备忘录状态及实现undo功能。DrawPanel类代码如下:

import java.awt.Color;
import java.awt.Graphics;

import javax.swing.JPanel;

public class DrawPanel  extends JPanel{
	
	private Color backColor = Color.white;  //背景色
	public DrawPanel() {
		this.setBackground(backColor);
	}
	public Memento createMemento(Point<Integer> fromPos,Point<Integer> toPos){
		Memento mem = new Memento(fromPos,toPos);
		return mem;
	}
	
	/**
	 *  撤销上一步操作
	 */
	public void undo(Memento mem){
		if(mem == null) return ;
		Point<Integer> from = mem.getFrom();
		Point<Integer> to = mem.getTo();
		
		Graphics g = this.getGraphics();
		g.setColor(backColor);
		System.err.println("restore pos :" + mem);
		//用画板背景色擦除
		g.drawLine(from.getX(), from.getY(), to.getX(), to.getY());
		
	}
	
	public Color getBackgroundColor(){
		return this.backColor;
	}

}

备忘录对象设计

备忘录对象用于记录对象状态。在本例中,备忘录记录每一步操作的起点及终点位置。Memento类代码如下:

import java.text.MessageFormat;

public class Memento {
	private Point<Integer> from;
	private Point<Integer> to;
	
	public Memento(Point<Integer> from, Point<Integer> to) {
		super();
		this.from = from;
		this.to = to;
	}

	public Point<Integer> getFrom() {
		return from;
	}

	public void setFrom(Point<Integer> from) {
		this.from = from;
	}

	public Point<Integer> getTo() {
		return to;
	}

	public void setTo(Point<Integer> to) {
		this.to = to;
	}
	
	/**
	 *  格式化输出,便于追踪问题
	 */
	public String toString(){
		String desc = "from ({0},{1}) to ({2},{3})";
		return MessageFormat.format(desc, from.getX(),from.getY(),to.getX(),to.getY());
	}
	
}

Memento类里面引用的状态对象Point表示一个位置点,Point类代码如下:

public class Point<T> {

	private T x;
	private T y;
	
	public Point(T x, T y) {
		super();
		this.x = x;
		this.y = y;
	}
	public T getX() {
		return x;
	}
	public void setX(T x) {
		this.x = x;
	}
	public T getY() {
		return y;
	}
	public void setY(T y) {
		this.y = y;
	}
	
}

负责人对象设计

在本例中,负责人(Carataker)类使用一个栈的数据结构来保存用户每一步操作的顺序。当用户需要undo操作,从堆栈里弹出最近一次的备忘录给原发者,原发者再将当前状态进行还原。当堆栈为空时,用户不能进行undo操作。Carataker类代码如下:

import java.util.Stack;

public class Caretaker {
	Stack<Memento> stack ;
	
	public Caretaker(){
		stack = new Stack<Memento>();
	}
	
	public Memento getMemento(){
		if(stack.isEmpty()) return null;
		
		Memento memento = stack.pop();
		return memento;
	}
	
	public void saveMemento(Memento memento){
		System.err.println("save pos :"+memento);
		stack.push(memento);
	}

}

应用程序入口(GUI界面展示),Application类代码如下:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
 *  简单画图板程序
 */
public class Application extends Frame implements MouseListener ,MouseMotionListener{
	private DrawPanel drawPanel = new DrawPanel();//原发者
	private Caretaker caretaker = new Caretaker();
	private int mouseX = 0;
	// 上一次 X 坐标位置
	private int lastMouseX = 0;
	// 鼠标 Y 坐标的位置
	private int mouseY = 0;
	// 上一次 Y 坐标位置
	private int lastMouseY = 0;
	// 画笔颜色
	private Color penColor = Color.black;
	
	public Application() {
		// 设置标题栏文字
		super("画图板");
	
		drawPanel.addMouseListener(this);
		drawPanel.addMouseMotionListener(this);
		// 将画图板添加到窗体中
		this.add(drawPanel);
		// 添加窗口监听,点击关闭按钮时退出程序
		this.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});

		// 设置窗口的大小
		this.setSize(new Dimension(400, 430));
		// 设置窗口位置,处于屏幕正中央
		this.setLocationRelativeTo(null);
		// 显示窗口
		this.setVisible(true);
	}

	/**
	 * 重写 paint 绘图方法
	 */
	public void paint() {
		Graphics g = this.drawPanel.getGraphics();
		g.setColor(penColor);
		g.drawLine(lastMouseX, lastMouseY, mouseX, mouseY);
	}

	public void mouseClicked(MouseEvent mouseEvent) {
		int btnType = mouseEvent.getButton();
		if(btnType == MouseEvent.BUTTON2){
			//点击中键,清空图片
			Graphics g = drawPanel.getGraphics();
			g .setColor(drawPanel.getBackgroundColor());
			g.clearRect(0,0,drawPanel.getWidth(),drawPanel.getHeight());
		}else if(btnType == MouseEvent.BUTTON3){
			//点击右键,撤销上一步操作
			Memento mem = caretaker.getMemento();
			//通过使用面板背景色画线来模拟直线擦除效果
			drawPanel.undo(mem);
		}
	}

	/**
	 * 鼠标按下
	 */
	public void mousePressed(MouseEvent mouseEvent) {
		this.lastMouseX = this.mouseX = mouseEvent.getX();
		this.lastMouseY = this.mouseY = mouseEvent.getY();
	}

	public void mouseReleased(MouseEvent mouseEvent) {
		int btnType = mouseEvent.getButton();
		if(btnType != MouseEvent.BUTTON1)  return;  //只捕捉鼠标左键
		
		this.lastMouseX = this.mouseX;
		this.lastMouseY = this.mouseY;
		this.mouseX = mouseEvent.getX();
		this.mouseY = mouseEvent.getY();
		
		Point<Integer> from = new Point<Integer>(this.lastMouseX,this.lastMouseY);
		Point<Integer> to = new Point<Integer>(this.mouseX,this.mouseY);
		caretaker.saveMemento(drawPanel.createMemento(from, to));
		paint() ;
	}

	public void mouseDragged(MouseEvent mouseEvent) {}
	public void mouseMoved(MouseEvent mouseEvent) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	
	
	public static void main(String[] args) {
		new Application();
	}

}

六、运行截图

程序运行界面如下


 

依次点击鼠标右键,效果如下

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jforgame

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值