从王者荣耀看设计模式
一.简介
点击王者荣耀商店法术的装备栏,存在有"贤者的庇护"这一装备。在对战中,当玩家处于死亡状态时,会触发此装备的效果,能够让英雄重新复活。
┬┴┬┌─ ●─┬─ │─┼─┐ ●├─┤○
┴┬┴├┬ ┌─┼─ │◎ │ │ ○└┬┘●
─┼─││ │ │ ││─┴─┴ ──┼──
●│○││ ┴─┼─ │○ ● / │ \
二.模式动机
在应用软件的开发中,很多时候我们都需要记录一个对象的内部状态。在具体实现时,为了允许用户取消不确定的操作或从错误中恢复过来,需要设置备份点并提供撤销机制,而要实现这些机制,必须事先将状态信息保存在某处,这样才能将对象恢复到它们原先的状态。备忘录模式是一种给我们的软件提供后悔药的机制,通过它可以使系统恢复到某一特定的历史状态。在本实例中,装备提供备份的功能,将英雄死亡之前的状态备份,英雄死亡触发备份事件将备份状态取出
三.备忘录模式
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是种对象行为型模式
备忘录模式的应用场景
在以下情况下可以使用备忘录模式
■ 保存一个对象在某一个时刻的状态或部分状态,这样以后需要时它能够恢复到先前的状态
■ 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过负责人可以间接访问其内部状态
备忘录模式的通用类图
备忘录模式涉及的角色
备忘录模式包含以下角色
⑴.Originator(原发器):原发器可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般需要保存内部状态的类设计为原发器,如一个存储用户信息或商品信息的对象
⑵.Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参照原发器的设计,根据实际需要来确定备忘录中的属性,需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用
⑶. Caretaker(负责人)
负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查,在负责人对象中可以存储一个或多个对象,它只负责存储对象,而不能修改对象,也无法知道对象的具体实现细节
备忘录模式优点:
⑴.提供一种状态恢复的实现机制
⑵.实现了信息的封装,使用堆栈来存储备忘录对象可以实现多次撤销操作
备忘录模式缺点:
资源消耗大,每保存一次状态都要耗费内存资源
四.结构图
五.代码实现
Originator类
package com.practice.memento;
/*
* 创建原发器类,存储当前的内部状态
*/
public class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
//创建备忘录,存储当前状态
public Memento createMemento() {
return new Memento(state);
}
//将存储的状态从备忘录中取出
public void restoreMemento(Memento m) {
this.setState(m.getState());
}
}
Memento类
package com.practice.memento;
/*
* 创建备忘录类,存储原发器的内部状态
*/
public class Memento {
private String state;
//接收原发器的内部状态
public Memento(String state) {
this.state = state;
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
Caretaker类
package com.practice.memento;
/*
* 创建负责人类,负责保存备忘录,存储备忘录对象
*/
public class Caretaker {
private Memento memento;
public void setMemento(Memento m) {
memento = m;
}
public Memento getMemento() {
return memento;
}
}
Client类
package com.practice.memento;
public class Client {
public static void main(String[] args) {
//创建原发器对象
Originator or = new Originator();
//创建负责人对象
Caretaker cr = new Caretaker();
//设置原发器当前状态
or.setState("You are still alive!");
System.out.println("初始状态:" + or.getState());
//将原发器当前状态存入备忘录中
cr.setMemento(or.createMemento());
//设置原发器新状态
or.setState("You have been slained!");
System.out.println("新的状态" + or.getState());
//将初始状态从备忘录中取出
or.restoreMemento(cr.getMemento());
System.out.println("恢复状态:" + or.getState());
}
}
运行结果:
六.备忘录模式的应用
实例说明
利用栈来存储备忘录对象是一种常见的行为,在本实例中,在GUI界面中一共有四位英雄可以供玩家选择,玩家选择完后按确定键确定选择,按返回键返回上次选择状态。
结构类图
代码实现
Client类
package com.practice.memento;
public class Client {
public static void main(String[] args) {
new HeroGame();
}
}
HeroGame类
package com.practice.memento;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
public class HeroGame extends JFrame implements ActionListener{
/**
* 客户类
*/
private static final long serialVersionUID = 1L;
JPanel CenterJP, EastJP;
JRadioButton hero1, hero2, hero3, hero4;
JButton button1, button2;
String FileName;
JLabel g;
PlayerOriginator po;
HeroStack hs;
public HeroGame() {
super("王者荣耀与备忘录模式");
po = new PlayerOriginator();
hs = new HeroStack();
this.setBounds(0,0,900,380);
this.setResizable(false);
FileName = "src/com/practice/photo/hero.jpg";
g = new JLabel(new ImageIcon(FileName),JLabel.CENTER);
CenterJP = new JPanel();
CenterJP.setLayout(new GridLayout(1,4));
CenterJP.setBorder(BorderFactory.createTitledBorder("英雄如下:"));
CenterJP.add(g);
this.add("Center",CenterJP);
EastJP = new JPanel();
EastJP.setLayout(new GridLayout(1,1));
EastJP.setBorder(BorderFactory.createTitledBorder("您选择的英雄是:"));
this.add("East",EastJP);
JPanel SouthJP = new JPanel();
JLabel info = new JLabel("您要选择四位英雄中的哪一位?");
hero1 = new JRadioButton("伽罗",true);
hero2 = new JRadioButton("小乔");
hero3 = new JRadioButton("大乔");
hero4 = new JRadioButton("娜可露露");
button1 = new JButton("确定");
button2 = new JButton("返回");
ButtonGroup group = new ButtonGroup();
group.add(hero1);
group.add(hero2);
group.add(hero3);
group.add(hero4);
SouthJP.add(info);
SouthJP.add(hero1);
SouthJP.add(hero2);
SouthJP.add(hero3);
SouthJP.add(hero4);
SouthJP.add(button1);
SouthJP.add(button2);
button1.addActionListener(this);
button2.addActionListener(this);
this.add("South",SouthJP);
showPicture("空白");
po.setHero("空白");
hs.push(po.createMemento());
}
//显示图片
void showPicture(String name) {
EastJP.removeAll();
EastJP.repaint();
po.setHero(name);
FileName = "src/com/practice/photo/" + name + ".png";
g = new JLabel(new ImageIcon(FileName),JLabel.CENTER);
EastJP.add(g);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
boolean ok = false;
if(e.getSource() == button1) {
ok = hs.push(po.createMemento());//保存状态
if(ok && hero1.isSelected()) {
showPicture("伽罗");
}else if(ok && hero2.isSelected()) {
showPicture("小乔");
}else if(ok && hero3.isSelected()) {
showPicture("大乔");
}else if(ok && hero4.isSelected()) {
showPicture("娜可露露");
}
}else if(e.getSource() == button2) {
po.restoreMemento(hs.pop());//恢复状态
showPicture(po.getHero());
}
}
}
PlayerOriginator类
package com.practice.memento;
/*
* 发起类
*/
public class PlayerOriginator {
private String heroName;
public void setHero(String heroName) {
this.heroName = heroName;
}
public String getHero() {
return heroName;
}
public Hero createMemento() {
return new Hero(heroName);
}
public void restoreMemento(Hero p) {
setHero(p.getHero());
}
}
Hero类
package com.practice.memento;
/*
* 备忘录类
*/
public class Hero {
private String name;
public Hero(String name) {
this.name = name;
}
public void setHero(String name) {
this.name = name;
}
public String getHero() {
return name;
}
}
HeroStack类
package com.practice.memento;
/*
* 管理者类
*/
public class HeroStack {
private Hero hero[];
private int top;
public HeroStack() {
hero = new Hero[10];
top = -1;
}
public boolean push(Hero p) {
if(top >= 10) {
System.out.println("放过小老弟,记不住了");
return false;
}else {
hero[++top] = p;
return true;
}
}
public Hero pop() {
if(top <= 0) {
System.out.println("英雄栈空了!");
return hero[0];
}else
return hero[top--];
}
}
运行结果: