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

背景

如图,这是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();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值