从王者荣耀看设计模式(十七.原型模式)
从王者荣耀看设计模式(原型模式)
一.简介
王者荣耀包含有很多的玩法,其中有一种游戏模式只在周六和周日开放,那就是——克隆模式。与常规的游戏双方阵营只允许出现单一英雄不同,在克隆模式中,双方各选择一个英雄进行克隆,换句话说,一个阵营的五个人操作的五个相同的英雄
二.模式动机
在软件系统中,有些对象的创建过程比较复杂,而且有时候需要频繁创建,原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象,这就是原型模式的意图所在。
三.原型模式
原型模式(Prototype Pattern):原型模式是一种对象创建型模式,用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。原型模式允许一个对象在创建另外一个定制的对象,无须知道任何创建的细节。原型模式的基本工作原理是通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象复制原型来实现创建过程。
建造者模式应用场景
在以下情况下可以使用原型模式:
⑴. 创建新对象成本较大(如初始化需要占用较大的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其属性进行修改.
⑵. 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好
⑶. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便
原型模式涉及的设计原则有:
★ 为交互对象之间的松耦合设计而努力
★ 软件实体应该是可以扩展的,但是不可修改的
原型模式通用类图:
原型模式涉及的角色有:
● Prototype(抽象原型类)
抽象原型类是定义具有克隆自己的方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口
● ConcretePrototype(具体原型类)
具体原型类实现具体的克隆方法,在克隆方法中返回自己的一个克隆对象。
● Client(客户类)
让一个原型克隆自身,从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个对象,再通过调用该对象的克隆方法复制得到多个相同的对象。
原型模式的优点:
⑴. 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率
⑵. 可以动态增加或减少产品类。
⑶. 原型模式提供了简化的创建结构。工厂方法模式常常需要一个与产品类等级结构相同的等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的clone()方法实现的,无须专门的工厂类来创建产品
⑷. 可以采用深克隆的方式保存对象的状态。使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用
原型模式的缺点:
⑴. 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说并不是很难,但对已有的类进行改造时,不一定是一件容易的事情,必须修改其源代码,违背了"开闭原则"
⑵. 在实现深克隆时需要编写较为复杂的代码
深克隆与浅克隆
浅克隆
在浅克隆中,被复制对象的所有普通成员变量都具有与原来对象相同的值,而所有的对其他对象的引用仍然指向原来的对象
1.实例说明
王者荣耀克隆模式英雄形象的创建可以看为简单的浅克隆
2.结构图
3.设计类图
4.代码实现
创建原型类(BaiLiShouYue类)
需要注意的是能够实现克隆的Java类必须实现一个标识接口Cloneable,标识这个类支持复制。**如果一个类没有实现这个接口但是调用clone()这个方法,Java编译器将抛出CloneNotSupportedException异常
package com.practice.clonepattern; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; /* * 创建原型类 */ public class BaiLiShouYue extends JPanel implements Cloneable { private static final long serialVersionUID = 1L; public BaiLiShouYue() { JLabel l1 = new JLabel(new ImageIcon("src/bailishouyue.png")); this.add(l1); } //复制原型对象 public Object clone() { BaiLiShouYue blsy = null; try { blsy = (BaiLiShouYue)super.clone(); }catch(CloneNotSupportedException e) { System.out.println("拷贝百里守约失败!"); } return blsy; } }
创建访问类(ProtoTypeBaiLi)
package com.practice.clonepattern; import java.awt.Container; import java.awt.GridLayout; import javax.swing.JFrame; /* * 创建访问类 */ public class ProtoTypeBaiLi { public static void main(String[] args) { JFrame f = new JFrame("原型模式测试"); f.setLayout(new GridLayout(1,2)); Container contentPane = f.getContentPane(); BaiLiShouYue obj1 = new BaiLiShouYue(); contentPane.add(obj1); BaiLiShouYue obj2 = (BaiLiShouYue)obj1.clone();//obj2为Object对象的复制对象 contentPane.add(obj2); //测试 System.out.println("obj1 == obj2?"); System.out.println(obj1 == obj2); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }
代码运行结果
深克隆
在深克隆中被复制对象的所有普通成员变量也都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
1.实例说明
在克隆模式中,多个玩家操作多个百里守约这个英雄。在游戏中,百里守约会根据有无皮肤说不同的台词。
2.结构图
3.设计类图
4.代码实现
创建原型类(CloneGame类)
CloneGame作为具体原型类,由于实现的是深克隆,无须使用Object的clone()方法,因此无须实现Cloneable接口;可以通过序列化的方式实现深克隆,由于要将CloneGame类型的对象写入流中,因此CloneGame需要实现Serializable接口
package com.practice.clone; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; /* * 创建原型类 */ public class CloneGame implements Serializable { private static final long serialVersionUID = 1L; private GameInfor gameInfor = null; public CloneGame(String Player,String gameInfor,String display) { this.gameInfor = new GameInfor(Player,gameInfor,display); } public Object deepClone() throws IOException,ClassNotFoundException{ //将对象写入流中 ByteArrayOutputStream bao = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bao); oos.writeObject(this); //将对象从流中取出 ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); System.out.println("玩家系统复制成功!"); return (ois.readObject()); } public GameInfor getGameInfor() { return this.gameInfor; } }
创建成员对象类(GameInfor类)
作为CloneGame类中的成员对象,在深克隆中,GameInfor类型的对象也将要被写入流中,因此GameInfor类也要实现Serializable接口
package com.practice.clone; import java.io.Serializable; /* * 创建成员对象类 */ public class GameInfor implements Serializable { private static final long serialVersionUID = 1L; String player; String infor; String display; public GameInfor(String _player,String _infor,String _display) { this.player = _player; this.infor = _infor; this.display = _display; System.out.println("玩家系统创建成功!"); } public GameInfor() {} public void setPlayer(String player) { this.player = player; } public void setDisplay(String display) { this.display = display; } public void Introduce() { System.out.println(player + infor + display); } }
创建客户类(ProtoTypeTest类)
package com.practice.clone; import java.io.IOException; /* * 创建测试类 */ public class ProtoTypeTest { public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException{ CloneGame obj1 = new CloneGame("妙乌:","(百里守约)技能以狙击为主,在对抗赛有着很强的远程优势。","给我一个目标,还你一片寂静"); GameInfor gif = obj1.getGameInfor(); gif.Introduce(); CloneGame obj2 = (CloneGame)obj1.deepClone(); GameInfor gif1 = obj2.getGameInfor(); gif1.setPlayer("秋梨膏:"); gif1.setDisplay("准备篝火旁的晚餐,庆祝又活过一天"); gif1.Introduce(); } }
运行结果:
四.原型模式扩展
原型模式的一种改进形式是带原型管理器的原型模式
原型管理器(Prototype Manager)角色创建具体原型类的对象,并记录这一个被创建的对象。原型管理器的作用于工厂相似,其中定义了一个集合用于存储原型对象,如果需要某个对象的一个克隆,可以通过复制集合中对应的原型对象获得。在原型管理器中针对抽象原型类进行编程,方便扩展
1.实例说明
用带原型管理器的原型模式来生成包含百里守约"特工魅影"和"朱雀志"的皮肤效果
2.结构图
3.设计类图
4.代码实现
创建抽象原型类(BaiLi)
package com.practice.BaiLiEffection; /* * 创建抽象原型类 */ public interface BaiLi extends Cloneable { public Object clone(); //拷贝 public void display(); //计算面积 }
创建具体原型类(SpySkin(英雄百里守约特工魅影皮肤特效))
package com.practice.BaiLiEffection; /* * 创建具体原型类 */ public class SpySkin implements BaiLi { public Object clone() { SpySkin ss = null; try { ss = (SpySkin)super.clone(); System.out.println("拷贝成功!"); }catch(CloneNotSupportedException e) { System.out.println("拷贝失败!"); } return ss; } public void display() { System.out.println("(百里守约)特工魅影:有谁与我约定过什么吗?"); } }
**创建具体原型类(S(英雄百里守约朱雀志皮肤特效))
package com.practice.BaiLiEffection; /* * 创建具体原型类 */ public class Rosefinch implements BaiLi { public Object clone() { Rosefinch rf = null; try { rf = (Rosefinch)super.clone(); System.out.println("拷贝成功!"); }catch(CloneNotSupportedException e) { System.out.println("拷贝失败!"); } return rf; } public void display() { System.out.println("(百里守约)朱雀志:子弹上膛的声音,是我唱歌的先兆"); } }
创建原型管理工具(SkinManager)
package com.practice.protoTypeManager; import java.util.HashMap; import com.practice.BaiLiEffection.BaiLi; import com.practice.BaiLiEffection.Rosefinch; import com.practice.BaiLiEffection.SpySkin; /* * 创建原型管理工具 */ public class SkinManager { private HashMap<String,BaiLi> ht= new HashMap<String,BaiLi>(); public SkinManager() { ht.put("SpySkin", new SpySkin()); ht.put("Rosefinch", new Rosefinch()); } public void addSkin(String key,BaiLi obj) { ht.put(key, obj); } public BaiLi getSkin(String key) { BaiLi temp = ht.get(key); return (BaiLi) temp.clone(); } }
创建客户类(Client)
package com.practice.Client; import com.practice.BaiLiEffection.BaiLi; import com.practice.protoTypeManager.SkinManager; /* * 创建客户类 */ public class Client { public static void main(String [] args) { SkinManager sm = new SkinManager(); //复制英雄百里守约特工魅影特效 BaiLi obj1 = (BaiLi)sm.getSkin("SpySkin"); obj1.display(); //复制英雄百里守约朱雀志特效 BaiLi obj2 = (BaiLi)sm.getSkin("Rosefinch"); obj2.display(); BaiLi obj3 = (BaiLi)sm.getSkin("SpySkin"); obj3.display(); } }
代码运行结果: