1. 意图
在不破坏封装性的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
2. 动机
假如你开发一款文字编辑器应用程序。除了简单的文字编辑功能外,编辑器还要有设置文本格式和插入内嵌图片的等功能。后来,决定添加一个让用户能撤销施加在文本上的任何操作。
刚开始,打算用直接的方式实现该功能:程序在执行任何操作前会记录所有对象的状态,并将其保存。当需要撤销某个操作时,程序将从历史记录中获得最近的快照,然后使用它来恢复所有对象的状态。
但是,如何生成这些快照呢?先想到的是遍历对象的所有成员变量并将其数值复制保存。这需要对象本身没有严格的访问权限限制,但是,大多数对象会使用私有成员变量来存储重要数据,这样别人就无法轻易查看其中的内容。
假设所有对象都是public的,这种方式,仍存在其他的问题。未来,由于需求变化,可能会添加或删除一些成员变量。这需要对负责复制对象状态的类进行修改。
另外,为了让其他对象能保存或读取快照,很可能需要将快照的成员变量设为公有,而这将暴漏被复制对象的状态。其他类也会对快照类的每个小改动产生依赖。我们似乎走进了一个死胡同:要么暴漏类的所有内部细节而使其过去脆弱;要么限制对其状态的访问权限而无法生成快照。
我们刚刚遇到的问题,都是封装破损造成的。一些对象试图超出其职责范围的工作。由于在执行某些行为时需要获取数据,所以它们侵入了其他对象的私有空间,而不是让这些对象来完成实际的工作
备忘录模式将创建状态快照(Snapshot)的工作委派给实际状态的拥有者原发器(Originator)对象。这样其他对象就不再需要从 "外部" 复制编辑器状态了,编辑器类拥有其状态的完全访问权,因此可以自行生成快照。模式建议将对象状态的副本存储在一个名为备忘录 (Memento) 的特殊对象中。 除了创建备忘录的对象外, 任何对象都不能访问备忘录的内容。 其他对象必须使用受限接口与备忘录进行交互, 它们可以获取快照的元数据 (创建时间和操作名称等), 但不能获取快照中原始对象的状态
3. 适用性
- 必须保存一个对象在某个时刻的(部分)状态,这样以后需要时他才能恢复到先前的状态
- 如果一个接口让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性
4. 结构
5. 效果
1) 可以在不破坏对象封装情况的前提下创建对象状态快照
2) 简化了原发器 在其他的保持封装性的设计中,Originator负责保持客户请求过的内部状态版本。这就把所有存储管理的责任交给了Originator。让客户管理请求的状态可以简化Originator,并且使得客户工作结束时无需通知原发器
3) 使用备忘录可能代价很高 如果原生器在生成备忘录时必须拷贝并存储大量的信息,或者客户非常频繁地创建备忘录和恢复原发器的状态,可能导致很大的开销。除非封装和恢复Originator状态的开销不大。
-存储增量式改变 如果备忘录的创建及其返回的顺序是可预测的,备忘录可以仅存储原发器内部状态的增量改变
例如,一个包含可撤销命令的历史列表可使用备忘录, 以保证命令被取消时他们可以恢复到正确的状态。历史列表定义了一个特定的顺序,按照这个顺序命令可以被撤销和重做。这意味着一个命令可以只存储一个命令所产生的增量改变而不是它所影响的每一个对象的完整状态。
6. 代码实现
假设开发一个图形编辑器的撤销功能,其允许修改屏幕上形状的颜色和位置。但任何修改都可被撤销和重复。"撤销" 功能基于备忘录和命令模式的合作。编辑器记录命令的执行历史。在执行任何命令之前,都会生成备份并将其连接到一个命令对象。而在执行完成后,会将已执行的命令放入历史记录中。当用户请求撤销操作时,编辑器将从历史记录中获取最近的命令,恢复在该命令内部保存的状态备份。如果用户再次请求撤销操作,编辑器将恢复历史记录中的下一个命令,以此类推。被撤销的命令都将保存在历史记录中,直至用户对屏幕上的形状进行了修改。这对恢复被撤销的命令来说至关重要
editor/Editor.java:编辑器代码
package memento.editor;import memento.history.History;import memento.history.Memento;import command.commands.Command;import composite.shapes.CompoundShape;import memento.shapes.Shape;import javax.swing.*;import java.io.*;import java.util.Base64;/** * @author GaoMing * @date 2021/7/25 - 20:47 */public class Editor extends JComponent { private Canvas canvas; private CompoundShape allShapes = new CompoundShape(); private History history; public Editor() { canvas = new Canvas(this); history = new History(); } public void loadShapes(Shape... shapes) { allShapes.clear(); allShapes.add(shapes); canvas.refresh(); } public CompoundShape getShapes() { return allShapes; } public void execute(Command c) { history.push(c, new Memento(this)); c.execute(); } public void undo() { if (history.undo()) canvas.repaint(); } public void redo() { if (history.redo()) canvas.repaint(); } public String backup() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this.allShapes); oos.close(); return Base64.getEncoder().encodeToString(baos.toByteArray()); } catch (IOException e) { return ""; } } public void restore(String state) { try { byte[] data = Base64.getDecoder().decode(state); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); this.allShapes = (CompoundShape) ois.readObject(); ois.close(); } catch (ClassNotFoundException e) { System.out.print("ClassNotFoundException occurred."); } catch (IOException e) { System.out.print("IOException occurred."); } }}
editor/Canvas.java: 画布代码
package memento.editor;import memento.commands.ColorCommand;import memento.commands.MoveCommand;import memento.shapes.Shape;import javax.swing.*;import javax.swing.border.Border;import java.awt.*;import java.awt.event.*;import java.awt.image.BufferedImage;/** * @author GaoMing * @date 2021/7/25 - 20:47 */public class Canvas extends java.awt.Canvas{ private Editor editor; private JFrame frame; private static final int PADDING = 10; Canvas(Editor editor) { this.editor = editor; createFrame(); attachKeyboardListeners(); attachMouseListeners(); refresh(); } private void createFrame() { frame = new JFrame(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); JPanel contentPanel = new JPanel(); Border padding = BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING); contentPanel.setBorder(padding); contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); frame.setContentPane(contentPanel); contentPanel.add(new JLabel("Select and drag to move."), BorderLayout.PAGE_END); contentPanel.add(new JLabel("Right click to change color."), BorderLayout.PAGE_END); contentPanel.add(new JLabel("Undo: Ctrl+Z, Redo: Ctrl+R"), BorderLayout.PAGE_END); contentPanel.add(this); frame.setVisible(true); contentPanel.setBackground(Color.LIGHT_GRAY); } private void attachKeyboardListeners() { addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0) { switch (e.getKeyCode()) { case KeyEvent.VK_Z: editor.undo(); break; case KeyEvent.VK_R: editor.redo(); break; } } } }); } private void attachMouseListeners() { MouseAdapter colorizer = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (e.getButton() != MouseEvent.BUTTON3) { return; } Shape target = editor.getShapes().getChildAt(e.getX(), e.getY()); if (target != null) { editor.execute(new ColorCommand(editor, new Color((int) (Math.random() * 0x1000000)))); repaint(); } } }; addMouseListener(colorizer); MouseAdapter selector = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (e.getButton() != MouseEvent.BUTTON1) { return; } Shape target = editor.getShapes().getChildAt(e.getX(), e.getY()); boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK; if (target == null) { if (!ctrl) { editor.getShapes().unSelect(); } } else { if (ctrl) { if (target.isSelected()) { target.unSelect(); } else { target.select(); } } else { if (!target.isSelected()) { editor.getShapes().unSelect(); } target.select(); } } repaint(); } }; addMouseListener(selector); MouseAdapter dragger = new MouseAdapter() { MoveCommand moveCommand; @Override public void mouseDragged(MouseEvent e) { if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != MouseEvent.BUTTON1_DOWN_MASK) { return; } if (moveCommand == null) { moveCommand = new MoveCommand(editor); moveCommand.start(e.getX(), e.getY()); } moveCommand.move(e.getX(), e.getY()); repaint(); } @Override public void mouseReleased(MouseEvent e) { if (e.getButton() != MouseEvent.BUTTON1 || moveCommand == null) { return; } moveCommand.stop(e.getX(), e.getY()); editor.execute(moveCommand); this.moveCommand = null; repaint(); } }; addMouseListener(dragger); addMouseMotionListener(dragger); } public int getWidth() { return editor.getShapes().getX() + editor.getShapes().getWidth() + PADDING; } public int getHeight() { return editor.getShapes().getY() + editor.getShapes().getHeight() + PADDING; } void refresh() { this.setSize(getWidth(), getHeight()); frame.pack(); } public void update(Graphics g) { paint(g); } public void paint(Graphics graphics) { BufferedImage buffer = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D ig2 = buffer.createGraphics(); ig2.setBackground(Color.WHITE); ig2.clearRect(0, 0, this.getWidth(), this.getHeight()); editor.getShapes().paint(buffer.getGraphics()); graphics.drawImage(buffer, 0, 0, null); }}
View Code
history/History.java: 保存命令和备忘录的历史记录
package memento.history;import memento.commands.Command;import java.util.ArrayList;import java.util.List;/** * @author GaoMing * @date 2021/7/25 - 20:47 */public class History { private List<Pair> history = new ArrayList<Pair>(); private int virtualSize = 0; private class Pair { Command command; Memento memento; Pair(Command c, Memento m) { command = c; memento = m; } private Command getCommand() { return command; } private Memento getMemento() { return memento; } } public void push(Command c, Memento m) { if (virtualSize != history.size() && virtualSize > 0) { history = history.subList(0, virtualSize - 1); } history.add(new Pair(c, m)); virtualSize = history.size(); } public boolean undo() { Pair pair = getUndo(); if (pair == null) { return false; } System.out.println("Undoing: " + pair.getCommand().getName()); pair.getMemento().restore(); return true; } public boolean redo() { Pair pair = getRedo(); if (pair == null) { return false; } System.out.println("Redoing: " + pair.getCommand().getName()); pair.getMemento().restore(); pair.getCommand().execute(); return true; } private Pair getUndo() { if (virtualSize == 0) { return null; } virtualSize = Math.max(0, virtualSize - 1); return history.get(virtualSize); } private Pair getRedo() { if (virtualSize == history.size()) { return null; } virtualSize = Math.min(history.size(), virtualSize + 1); return history.get(virtualSize - 1); }}
history/Memento.java:备忘录类
package memento.history;import memento.editor.Editor;/** * @author GaoMing * @date 2021/7/25 - 20:48 */public class Memento { private String backup; private Editor editor; public Memento(Editor editor) { this.editor = editor; this.backup = editor.backup(); } public void restore() { editor.restore(backup); }}
commands/Command.java: 基础命令类
package memento.commands;/** * @author GaoMing * @date 2021/7/25 - 20:52 */public interface Command { String getName(); void execute();}
commands/ColorCommand.java: 修改已选形状的颜色
package memento.commands;import memento.editor.Editor;import memento.shapes.Shape;import java.awt.*;/** * @author GaoMing * @date 2021/7/25 - 20:52 */public class ColorCommand implements Command{ private Editor editor; private Color color; public ColorCommand(Editor editor, Color color) { this.editor = editor; this.color = color; } @Override public String getName() { return "Colorize: " + color.toString(); } @Override public void execute() { for (Shape child : editor.getShapes().getSelected()) { child.setColor(color); } }}
commands/MoveCommand.java: 移动已选形状
package memento.commands;import memento.editor.Editor;import memento.shapes.Shape;/** * @author GaoMing * @date 2021/7/25 - 20:53 */public class MoveCommand implements Command{ private Editor editor; private int startX, startY; private int endX, endY; public MoveCommand(Editor editor) { this.editor = editor; } @Override public String getName() { return "Move by X:" + (endX - startX) + " Y:" + (endY - startY); } public void start(int x, int y) { startX = x; startY = y; for (Shape child : editor.getShapes().getSelected()) { child.drag(); } } public void move(int x, int y) { for (Shape child : editor.getShapes().getSelected()) { child.moveTo(x - startX, y - startY); } } public void stop(int x, int y) { endX = x; endY = y; for (Shape child : editor.getShapes().getSelected()) { child.drop(); } } @Override public void execute() { for (Shape child : editor.getShapes().getSelected()) { child.moveBy(endX - startX, endY - startY); } }}
shapes/Shape.java
package memento.shapes;import java.awt.*;import java.io.Serializable;/** * @author GaoMing * @date 2021/7/25 - 20:55 */public interface Shape extends Serializable { int getX(); int getY(); int getWidth(); int getHeight(); void drag(); void drop(); void moveTo(int x, int y); void moveBy(int x, int y); boolean isInsideBounds(int x, int y); Color getColor(); void setColor(Color color); void select(); void unSelect(); boolean isSelected(); void paint(Graphics graphics);}
shapes/BaseShape.java
package memento.shapes;import java.awt.*;/** * @author GaoMing * @date 2021/7/25 - 20:56 */public abstract class BaseShape implements Shape{ int x, y; private int dx = 0, dy = 0; private Color color; private boolean selected = false; BaseShape(int x, int y, Color color) { this.x = x; this.y = y; this.color = color; } @Override public int getX() { return x; } @Override public int getY() { return y; } @Override public int getWidth() { return 0; } @Override public int getHeight() { return 0; } @Override public void drag() { dx = x; dy = y; } @Override public void moveTo(int x, int y) { this.x = dx + x; this.y = dy + y; } @Override public void moveBy(int x, int y) { this.x += x; this.y += y; } @Override public void drop() { this.x = dx; this.y = dy; } @Override public boolean isInsideBounds(int x, int y) { return x > getX() && x < (getX() + getWidth()) && y > getY() && y < (getY() + getHeight()); } @Override public Color getColor() { return color; } @Override public void setColor(Color color) { this.color = color; } @Override public void select() { selected = true; } @Override public void unSelect() { selected = false; } @Override public boolean isSelected() { return selected; } void enableSelectionStyle(Graphics graphics) { graphics.setColor(Color.LIGHT_GRAY); Graphics2D g2 = (Graphics2D) graphics; float dash1[] = {2.0f}; g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 2.0f, dash1, 0.0f)); } void disableSelectionStyle(Graphics graphics) { graphics.setColor(color); Graphics2D g2 = (Graphics2D) graphics; g2.setStroke(new BasicStroke()); } @Override public void paint(Graphics graphics) { if (isSelected()) { enableSelectionStyle(graphics); } else { disableSelectionStyle(graphics); } // ... }}
View Code
shapes/Circle.java
package memento.shapes;import java.awt.*;/** * @author GaoMing * @date 2021/7/25 - 20:57 */public class Circle extends BaseShape{ private int radius; public Circle(int x, int y, int radius, Color color) { super(x, y, color); this.radius = radius; } @Override public int getWidth() { return radius * 2; } @Override public int getHeight() { return radius * 2; } @Override public void paint(Graphics graphics) { super.paint(graphics); graphics.drawOval(x, y, getWidth() - 1, getHeight() - 1); }}
shapes/Dot.java
package memento.shapes;import java.awt.*;/** * @author GaoMing * @date 2021/7/25 - 20:57 */public class Dot extends BaseShape{ private final int DOT_SIZE = 3; public Dot(int x, int y, Color color) { super(x, y, color); } @Override public int getWidth() { return DOT_SIZE; } @Override public int getHeight() { return DOT_SIZE; } @Override public void paint(Graphics graphics) { super.paint(graphics); graphics.fillRect(x - 1, y - 1, getWidth(), getHeight()); }}
shapes/Rectangle.java
package memento.shapes;import java.awt.*;/** * @author GaoMing * @date 2021/7/25 - 20:58 */public class Rectangle extends BaseShape{ private int width; private int height; public Rectangle(int x, int y, int width, ......原文转载:http://www.shaoqun.com/a/892283.html
跨境电商:https://www.ikjzd.com/
网上1号店:https://www.ikjzd.com/w/2263
writer:https://www.ikjzd.com/w/1280
美菜:https://www.ikjzd.com/w/1874
1.意图 在不破坏封装性的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态 2.动机 假如你开发一款文字编辑器应用程序。除了简单的文字编辑功能外,编辑器还要有设置文本格式和插入内嵌图片的等功能。后来,决定添加一个让用户能撤销施加在文本上的任何操作。 刚开始,打算用直接的方式实现该功能:程序在执行任何操作前会记录所有对象的状态,并将其保存。当
四川温泉有哪些?四川温泉旅游线路推荐_峨眉山旅游攻略_峨眉山旅游景点:http://www.30bags.com/a/415422.html
四川汶川多地泥石流灾害 2人失联3人被困:http://www.30bags.com/a/424909.html
四川卧龙暴雨引发泥石流 12400名旅客被疏散:http://www.30bags.com/a/424927.html
四川卧龙暴雨致96人被困 当地疏散游客近11000人:http://www.30bags.com/a/425287.html
老师晚上求我桶她 老师好大好深啊把腿开开:http://lady.shaoqun.com/a/247844.html
男朋友公车猛烈的一进一出 又硬又粗又长爽死我了:http://lady.shaoqun.com/a/247283.html
学长大人上课和我做 学长的巨大在我体内进进出出:http://lady.shaoqun.com/a/247506.html
被两个男人抬起腿做 两根粗大一前一后好深:http://lady.shaoqun.com/m/a/247942.html
深圳宝安科技馆8月展览汇总(持续更新):http://www.30bags.com/a/517601.html
2021时尚深圳展蝶讯馆展览好看吗:http://www.30bags.com/a/517602.html
2021时尚深圳蝶讯馆观展攻略:http://www.30bags.com/a/517603.html
深圳欢乐谷夏浪音乐节有朱星杰吗:http://www.30bags.com/a/517604.html
No comments:
Post a Comment