如何实现GoF《设计模式》一书中备忘录模式的例子?

    科技2024-12-05  26

    背景

    如图,这是GoF的《设计模式》一书第五章中Memento备忘录模式动机一节的例子。

    应用介绍摘自Gang of Four的《设计模式》第五章行为型模式的备忘录模式: 例如,考虑一个图形编辑器,它支持图形对象之间的连线。用户可用一条直线连接两个矩形,者两个矩形仍能保持连接。在移动过程中,编辑器自带伸展这条直线以保持该连接。

    完成效果

    原书没有给出实现代码,所以我基于Java语言实现一个。 可拖拽任意一个矩形移动位置。可按Ctrl+Z撤销操作,回到图形上一次的位置。下面有完整代码和注释,读者可先尝试运行代码看看效果。

    如何实现

    定义一个Shape接口,其中Shape只有一个draw()方法。 分别定义Rect和Line类,实现Shape接口。 定义MyPanel继承Panel类,用于画板。保存2个矩形和一条直线,注册了鼠标监听器,实现拖动矩形的逻辑。

    Stack< MyRectMemento >保存2个矩形的位置信息,undoShape()用于撤销操作和恢复图形上一次的位置。其中MyRectMemento作为备忘录,下面讲备忘录模式的时候会提到 定义MyFrame,继承Frame。注册键盘监听器,用于监听Ctrl+Z组合键。 执行撤销逻辑。

    里面应用了一个模式——备忘录模式 定义MyRectMemento类作为MyRect类的备忘录,保存两个矩形的位置信息。 MyPanel作为Originator原发器和Caretaker管理者,原发器用于在鼠标点击事件的时候创建一个备忘录,记录当前矩形位置的状态。管理者利用内部变量Stack< MyRectMememto >保存好备忘录。当按Ctrl+Z时会触发在MyFrame类的键盘监听器,监听器会执行撤销逻辑。

    完整代码【基于Java】

    共分为6个类加上一个接口。

    Client

    public class Client { public static void main(String[] args) { MyFrame myFrame = new MyFrame(); } }

    Line

    import java.awt.*; public class Line implements Shape { private int xBegin,yBegin,xEnd,yEnd; private Color color; private Graphics g; public Line(int xBegin, int yBegin, int xEnd, int yEnd, Graphics g, Color color) { this.xBegin = xBegin; this.yBegin = yBegin; this.xEnd = xEnd; this.yEnd = yEnd; this.g = g; this.color = color; } @Override public void draw() { g.setColor(color); g.drawLine(xBegin,yBegin,xEnd,yEnd); } public void setxBegin(int xBegin) { this.xBegin = xBegin; } public void setyBegin(int yBegin) { this.yBegin = yBegin; } public void setxEnd(int xEnd) { this.xEnd = xEnd; } public void setyEnd(int yEnd) { this.yEnd = yEnd; } }

    MyFrame

    import javax.swing.*; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; public class MyFrame extends JFrame { MyPanel myPanel; public MyFrame() { this.myPanel = new MyPanel(); add(myPanel);//把画布装入边框 setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//设置窗口关闭方式 Dimension dim = this.getToolkit().getScreenSize();// 获取屏幕分辨率 setBounds(dim.width / 4, dim.height / 4, (dim.width / 2 + 40),dim.height / 2);// 窗体初始大小及定位 setTitle("GoF备忘录模式例子实现(可拖动图形,按Ctrl+Z可撤销操作)"); setVisible(true);//使组件可见 /* 因为在所有组件还没画出时,是不能获得Graphics的,只会返回Null,画图形操作放在setVisible(true)之后 另外,paint()画操作是基于JFrame的,因为我们要重新画整个界面,从而消除单个图形之前的痕迹。 读者可调用语句顺序尝试一下。 */ myPanel.initShape();//获得JPanel的画笔,并初始化图形对象 addKeyListener();//绑定键盘监听器 } @Override public void paint(Graphics g) { super.paint(g); myPanel.paint(g);//通过JPanel的画笔g画出图形 } /** * 绑定键盘监听器:当点击Ctrl+Z时,执行撤销操作 */ private void addKeyListener(){ addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if ((e.isControlDown() == true) && (e.getKeyCode() == KeyEvent.VK_Z)) { myPanel.undoShape(); } } }); } }

    MyPanel

    import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.util.Stack; public class MyPanel extends JPanel { private Rect rect1; private Rect rect2; private Line line; private int xBefore,yBefore; private Stack<MyRectMemento> rectList = new Stack<>();//保存2个矩形位置的历史记录 public MyPanel() { Dimension dim = this.getToolkit().getScreenSize();// 获取屏幕分辨率 setPreferredSize(new Dimension((dim.width / 2 + 40),dim.height / 2));// 画布初始大小及定位 setBackground(Color.white);//设置背景色为白色 } public void initShape(){ Graphics g = getGraphics(); this.rect1 = new Rect(100,100,100,100,g,Color.blue); this.rect2 = new Rect(300,300,100,100,g,Color.green); this.line = new Line(150,150,350,350,g,Color.red); rectList.push(new MyRectMemento(rect1.getX(),rect1.getY(),rect2.getX(),rect2.getY()));//记录2个矩形的初始位置 addMouseListener(); addMouseMotionListener(); } @Override public void paint(Graphics g) { super.paint(g); rect1.draw(); rect2.draw(); line.draw(); } /** * 绑定鼠标监听器 * 自定义鼠标点击,释放事件 */ private void addMouseListener(){ addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (isInTheRect(e.getX(),e.getY(),rect1)){ rect1.setSelected(true); xBefore = e.getX(); yBefore = e.getY(); }else if (isInTheRect(e.getX(),e.getY(),rect2)){ rect2.setSelected(true); xBefore = e.getX(); yBefore = e.getY(); } //赋值当前鼠标点击时2个矩形的位置 rectList.push(new MyRectMemento(rect1.getX(),rect1.getY(),rect2.getX(),rect2.getY())); } @Override public void mouseReleased(MouseEvent e) { rect1.setSelected(false); rect2.setSelected(false); } }); } private void addMouseMotionListener(){ addMouseMotionListener(new MouseMotionListener() { @Override public void mouseDragged(MouseEvent e) { if (rect1.isSelected() && checkPoint(e.getX(),e.getY())){ rect1.setX(rect1.getX() + (e.getX() - xBefore)); rect1.setY(rect1.getY() + (e.getY() - yBefore)); xBefore = e.getX(); yBefore = e.getY(); line.setxBegin(rect1.getX() + 50); line.setyBegin(rect1.getY() + 50); }if (rect2.isSelected() && checkPoint(e.getX(),e.getY())){ rect2.setX(rect2.getX() + (e.getX() - xBefore)); rect2.setY(rect2.getY() + (e.getY() - yBefore)); xBefore = e.getX(); yBefore = e.getY(); line.setxEnd(rect2.getX() + 50); line.setyEnd(rect2.getY() + 50); } //重画图形 MyFrame myFrame = (MyFrame) getParent().getParent().getParent().getParent(); myFrame.paint(getGraphics()); } @Override public void mouseMoved(MouseEvent e) { } }); } /** * //检测 点(mx,my) 是否在矩形1上 * @param mx 鼠标当前x坐标 * @param my 鼠标当前y坐标 * @param rect 当前矩形对象 * @return true or false */ private boolean isInTheRect(int mx, int my,Rect rect){ int x = rect.getX(),y = rect.getY(), height = rect.getHeight(),width = rect.getWidth(); if (mx >= x && mx <= x + width && my >= y && my <= y + height){ return true; }else { return false; } } /** * 检查鼠标当前位置是否超出应用边界 * @param mx 鼠标当前x坐标 * @param my 鼠标当前y坐标 * @return true or false */ private boolean checkPoint(int mx, int my){ if (mx < 0 || my < 0 || mx > getWidth() || my > getHeight()) { return false; }else { return true; } } public Stack<MyRectMemento> getRectList() { return rectList; } /** * 当按Ctrl+Z时 恢复至图形上一次的位置 */ public void undoShape(){ if (rectList.size() >= 1){ MyRectMemento memento = rectList.pop(); rect1.setX(memento.getX1()); rect1.setY(memento.getY1()); rect2.setX(memento.getX2()); rect2.setY(memento.getY2()); line.setxBegin(rect1.getX() + 50); line.setyBegin(rect1.getY() + 50); //重画图形 MyFrame myFrame = (MyFrame) getParent().getParent().getParent().getParent(); myFrame.paint(getGraphics()); } } }

    MyRectMemento

    /** * 作为MyRect的备忘录,保存两个矩形的位置信息x1,y1,z2,y2 */ public class MyRectMemento { private final int x1; private final int y1; private final int x2; private final int y2; public MyRectMemento(int x1, int y1, int x2, int y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } public int getX1() { return x1; } public int getY1() { return y1; } public int getX2() { return x2; } public int getY2() { return y2; } }

    Rect

    import java.awt.*; public class Rect implements Shape { private int x,y,height,width; private Graphics g; private Color color; private boolean isSelected = false; public Rect(int x, int y, int height, int width, Graphics g, Color color) { this.x = x; this.y = y; this.height = height; this.width = width; this.g = g; this.color = color; } @Override public void draw() { g.setColor(color); g.drawRect(x,y,width,height); } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public int getX() { return x; } public int getY() { return y; } public int getHeight() { return height; } public int getWidth() { return width; } public boolean isSelected() { return isSelected; } public void setSelected(boolean selected) { isSelected = selected; } }

    Shape

    public interface Shape { void draw(); }
    Processed: 0.010, SQL: 8