8,原型模式

您所在的位置:网站首页 dota猴子原型 8,原型模式

8,原型模式

#8,原型模式| 来源: 网络整理| 查看: 265

一,前言 上一篇说了建造者模式,这篇说创建型设计模式的最后一个:原型模式 实际开发中,有时需要为一个类创建多个实例,而有些类的实例化开销较大且耗时 所以我们希望通过复制已创建的对象来创建相同或相似的对象,以减少实例化开销 原型模式就是从一个对象创建另一个新的对象,使新的对象有具有原对象的特征的设计模式 在进行原型模式之前,我们需要先了解一下原型模式所涉及到的其他知识点 二,Java中的Cloneable接口 Java的所有类都是从java.lang.Object类继承而来,Object类提供protected Object clone()方法对对象进行复制 /*Cloneable接口源代码,JDK1.8*/ public interface Cloneable { }

发现一个奇怪的现象:Cloneable接口中没有定义任何的接口方法。

这是因为java的所有类都继承自Object,而Object将clone()定义为所有类都应该具有的基本功能。 Object中,clone()声明为了protected类型,该方法定义了逐字段拷贝实例的操作。 它是一个native本地方法,因此没有实现体,而且在拷贝字段时,除了Object类的字段外,其子类的新字段也将被拷贝到新的实例中。 /*Object类中clone()方法的定义*/ protected native Object clone() throws CloneNotSupportedException;

又一个问题:既然Object已经定义了clone方法,为什么还需要实现Cloneable接口?

Cloneable接口的官方javadoc文档: "Invoking Object's clone method on an instance that does not implement the Cloneable interface results in the exception CloneNotSupportedException being thrown. JDK1.8" 如果不实现该接口直接调用clone()方法,即使将clone()方法重写为public,还是会抛出“不支持拷贝”异常。 所以要想实现拷贝空能需要满足以下两点: 1,实现Cloneable接口 2,重写Object类的clone()方法 clone是直接在内存中复制数据,因此不会调用到类的构造方法。 不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效 三,深复制,浅复制和复制深度 对象通常都会包含对其他对象的引用,这里就涉及到深复制,浅复制,复制深度的问题 浅复制:基本数据类型的变量会重新创建,而引用类型指向原对象的引用 深复制:基本数据类型和引用类型都会重新创建 复制深度:对于对层嵌套式的引用类型,需要考虑赋值深度到哪一层合适,当然也要考虑死循环的问题 深复制是完全彻底的复制,而浅复制不彻底。 Object类的clone方法只拷贝对象中的基本数据类型 (8种基本数据类型byte,char,short,int,long,float,double,boolean) 不会拷贝数组、容器对象、引用对象 数组的拷贝方法: System.arraycopy(array_old, start_index, array_copy, start_index, end_index) 4,利用序列化实现深度克隆 对于多层依赖的对象,要想实现深复制,可以使用序列化实现深度克隆 读入当前对象的二进制输入,再写出二进制数据对应的对象。 把对象写到流里的过程是序列化(Serialization)过程; 把对象从流中读出来的过程则叫反序列化(Deserialization)过程。 写到流里的是对象的拷贝,而原对象仍然存在于JVM里面。 Java中深度克隆对象,可以使对象实现Serializable接口, 然后把对象(实际上只是对象的拷贝)写到一个流里(序列化), 再从流里读回来(反序列化)    public Object deepClone() throws IOException, ClassNotFoundException{ //将对象写到流里 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); //从流里读回来 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } 前提是对象以及对象内部所有引用对象都是可序列化的,否则,需要仔细考察那些不可序列化的对象可否设成transient,从而排除在复制过程外。 线程(Thread)或Socket对象,是不能简单复制或共享的。 不管使用浅克隆还是深克隆,只要涉及这样的间接对象,必须把间接对象设成transient而不予复制; 或者由程序自行创建出相当的同种对象,当做复制件使用。 5,原型模式Demo场景分析 实现原型模式的基础就是clone,所以原型模式也需要满足两点: 1,实现Cloneable接口 2,重写Object类的clone()方法 下面我们用一个例子说明原型模式,深复制,浅复制和复制深度的问题

幻影长矛手

我们选择DOTA英雄-暗影长矛手(以下简称猴子),作为原型模式Demo DOTA游戏中,猴子的主要技能就是镜像(以下称分身),为了说明问题,我们把技能简化一下: 1,英雄本体初始生命值,魔法值,攻击力,防御力均为100 3,英雄本体使用技能,魔法值消耗10,并制造1个分身1 (分身继承英雄当前生命值和魔法值,并继承50%的攻击力和防御力) 4,分身1受到一次伤害生命值降低10,产生镜像1_1,消耗10魔法值 (同上,分身的分身继承分身当前的生命值和魔法值,并继承其50%的攻击力和防御力) 5,本体再次使用镜像技能,全部分身消失,重新制造1个镜像-分身2 游戏单位拥有名字,生命值,魔法值,攻击力,防御力,是否镜像这五种属性 使用以上例子,我们将探讨浅克隆,深克隆以及简单形式和登记形式的克隆 注:为了说明深克隆问题,将生命值,魔法值,攻击力和防御力属性合并为角色状态对象 1,开始,我们将使用浅克隆方式为猴子克隆分身,来说明浅克隆只能拷贝基础数据类型,而对于引用类型,还是指向原对象的引用地址 2,然后我们采用深克隆的方式为猴子克隆分身,并触发一次伤害,来验证深克隆的引用类型是单独拷贝的 3,以上均为克隆模式的简单形式,我们将所有的分身保存在一个分身管理器中, 无论当前有多少分身,一但本体使用一次镜像技能,立即销毁全部分身并重新制造一个分身 6,原型模式 一切都准备就绪了,我们来看代码

1)角色状态类:包含生命值,魔法值,攻击力,防御力

package com.brave.prototype.dota.monkey; import java.io.Serializable; /** * 角色状态类 包含生命值,魔法值 初始值为100 * * @author Brave * */ public class Status implements Serializable { private double HP = 100; // 生命值 private double MP = 100; // 魔法值 private double ATK = 100; // 攻击力 private double DEF = 100; // 防御力 public double getHP() { return HP; } public void setHP(double hP) { HP = hP; } public double getMP() { return MP; } public void setMP(double mP) { MP = mP; } public double getATK() { return ATK; } public void setATK(double aTK) { ATK = aTK; } public double getDEF() { return DEF; } public void setDEF(double dEF) { DEF = dEF; } }

2)定义原型接口(人物具备的功能)

package com.brave.prototype.dota.monkey; import java.io.IOException; public interface Prototype { public Prototype damage() throws IOException, ClassNotFoundException; public Prototype clone(String name) throws CloneNotSupportedException; public Prototype mirror(String name) throws IOException, ClassNotFoundException; public String getName(); public void setName(String name); public Status getStatus(); public void setStatus(Status status); public boolean isMirror(); public void setMirror(boolean isMirror); }

3)实现原型接口,创建幻影长矛手

package com.brave.prototype.dota.monkey; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Date; public class Monkey implements Prototype, Cloneable, Serializable { // 名称 private String name; // 状态(生命值, 魔法值) private Status status; // 是否镜像 private boolean isMirror = false; /** * 构造函数 */ public Monkey(String name) { this.name = name; this.status = new Status(); } /** * 浅克隆 Monkey实现Cloneable接口可以使用super.clone(); 本类中的clone方法可以随意命名 * 浅拷贝只复制基础数据类型,引用类型和原对象指向同一引用 */ @Override public Prototype clone(String name) throws CloneNotSupportedException { Monkey cloneMonkey = null; cloneMonkey = (Monkey) super.clone(); cloneMonkey.setName(name); return cloneMonkey; } // 深克隆 public Object deepClone() throws IOException, ClassNotFoundException { // 将对象写到流里 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); // 从流里读回来 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } // 受到伤害HP-10 @Override public Prototype damage() throws IOException, ClassNotFoundException { Status s = this.getStatus(); s.setHP(s.getHP() - 10); if(isMirror()){ return mirror(this.getName() + "_1"); } return null; } // 使用镜像技能MP-10,并制造1个镜像 @Override public Prototype mirror(String name) throws IOException, ClassNotFoundException { // 如果是本体使用镜像技能先清除全部分身 if(!isMirror()){ PrototypeManager.clearPrototype(); } // 消耗MP Status s = this.getStatus(); s.setMP(s.getMP() - 10); // 深复制对象,分身继承1/2攻击力防御力 Prototype deepClone = (Prototype) this.deepClone(); deepClone.setName(name); deepClone.setMirror(true); Status deepCloneStatus = deepClone.getStatus(); deepCloneStatus.setATK(deepCloneStatus.getATK() / 2); deepCloneStatus.setDEF(deepCloneStatus.getDEF() / 2); // 添加到分身管理器 System.out.println("制造一个分身并放入分身管理器, Name = " + name); PrototypeManager.setPrototype(deepClone.getName(), deepClone); return deepClone; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } @Override public Status getStatus() { return status; } @Override public void setStatus(Status status) { this.status = status; } @Override public boolean isMirror() { return isMirror; } @Override public void setMirror(boolean isMirror) { this.isMirror = isMirror; } }

4)添加一个管理类,管理所有分身对象

package com.brave.prototype.dota.monkey; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class PrototypeManager { /** * 用来记录原型的编号和原型实例的对应关系 */ private static Map map = new HashMap(); /** * 私有化构造方法,避免外部创建实例 */ private PrototypeManager() { } /** * 向原型管理器里面添加或是修改某个原型注册 * * @param prototypeId * 原型编号 * @param prototype * 原型实例 */ public synchronized static void setPrototype(String prototypeId, Prototype prototype) { map.put(prototypeId, prototype); } /** * 从原型管理器里面删除某个原型注册 * * @param prototypeId * 原型编号 */ public synchronized static void removePrototype(String prototypeId) { map.remove(prototypeId); } /** * 获取某个原型编号对应的原型实例 * * @param prototypeId * 原型编号 * @return 原型编号对应的原型实例 * @throws Exception * 如果原型编号对应的实例不存在,则抛出异常 */ public synchronized static Prototype getPrototype(String prototypeId) throws Exception { Prototype prototype = map.get(prototypeId); if (prototype == null) { throw new Exception("原型未找到,未注册或已销毁"); } return prototype; } /** * 移除所有原型 */ public synchronized static void clearPrototype() { map.clear(); System.out.println("销毁全部分身"); } public synchronized static void show() { Iterator entries = map.entrySet().iterator(); if(!entries.hasNext()){ System.out.println("空的分身管理器"); }else{ while (entries.hasNext()) { Map.Entry entry = (Map.Entry) entries.next(); String name = (String)entry.getKey(); Prototype prototype = (Prototype)entry.getValue(); Status status = prototype.getStatus(); System.out.println("Name = " + name + ", HP = "+ status.getHP() + ", MP = " + status.getMP() + ", ATK = " + status.getATK() + " , DEF = "+ status.getDEF()); } } } }

5)测试类

package com.brave.prototype.dota.monkey; import java.io.IOException; public class Client { public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException { System.out.println("*************开始测试浅克隆*************"); testClone(); System.out.println("*************开始测试深克隆*************"); testDeepClone(); System.out.println("*************开始测试登记形式的原型模式*************"); testManagerClone(); } private static void testClone() throws CloneNotSupportedException, IOException, ClassNotFoundException{ System.out.println("-----------实例化-幻影长矛手-----------"); System.out.println("-----------浅克隆-幻影长矛手-----------"); Prototype monkey = new Monkey("幻影长矛手"); Prototype cloneMonkey = monkey.clone("浅克隆-幻影长矛手"); System.out.println("-----------对比名称(基础数据类型)和状态(引用对象类型)-----------"); System.out.println("monkey-name = " + monkey.getName()); System.out.println("cloneMonkey-name = " + cloneMonkey.getName()); System.out.println( "对比引用类型monkey-status 和 cloneMonkey-status : " + (monkey.getStatus() == cloneMonkey.getStatus())); System.out.println("-----------浅克隆对象受伤HP-10-----------"); cloneMonkey.damage(); System.out.println("-----------对比本体和浅克隆体的生命值-----------"); System.out.println("monkey-HP = " + monkey.getStatus().getHP()); System.out.println("cloneMonkey-HP = " + cloneMonkey.getStatus().getHP()); } private static void testDeepClone() throws IOException, ClassNotFoundException { System.out.println("-----------实例化-幻影长矛手,并深克隆-----------"); Prototype monkey = new Monkey("幻影长矛手"); Prototype cloneMonkey = monkey.mirror("深克隆-幻影长矛手"); System.out.println("-----------对比状态对象(引用对象类型)-----------"); System.out.println( "对比引用类型monkey-status 和 cloneMonkey-status : " + (monkey.getStatus() == cloneMonkey.getStatus())); System.out.println("-----------深克隆对象受伤HP-10-----------"); cloneMonkey.damage(); System.out.println("-----------对比本体和深克隆体的生命值-----------"); System.out.println("monkey-HP = " + monkey.getStatus().getHP()); System.out.println("cloneMonkey-HP = " + cloneMonkey.getStatus().getHP()); PrototypeManager.clearPrototype(); } private static void testManagerClone() throws IOException, ClassNotFoundException { System.out.println("-----------实例化-幻影长矛手,并消耗10MP镜像(深克隆)出分身1-----------"); Prototype monkey = new Monkey("幻影长矛手"); Prototype cloneMonkey1 = monkey.mirror("分身1"); System.out.println("-----------分身1受到伤害HP-10,并消耗10MP创造出分身1_1-----------"); cloneMonkey1.damage(); System.out.println("-----------输出分身管理器-----------"); PrototypeManager.show(); System.out.println("-----------本体使用镜像技能-----------"); monkey.mirror("分身2"); System.out.println("-----------再次输出分身管理器-----------"); PrototypeManager.show(); } }

6)测试输出

*************开始测试浅克隆************* -----------实例化-幻影长矛手----------- -----------浅克隆-幻影长矛手----------- -----------对比名称(基础数据类型)和状态(引用对象类型)----------- monkey-name = 幻影长矛手 cloneMonkey-name = 浅克隆-幻影长矛手 对比引用类型monkey-status 和 cloneMonkey-status : true -----------浅克隆对象受伤HP-10----------- -----------对比本体和浅克隆体的生命值----------- monkey-HP = 90.0 cloneMonkey-HP = 90.0 *************开始测试深克隆************* -----------实例化-幻影长矛手,并深克隆----------- 销毁全部分身 制造一个分身并放入分身管理器, Name = 深克隆-幻影长矛手 -----------对比状态对象(引用对象类型)----------- 对比引用类型monkey-status 和 cloneMonkey-status : false -----------深克隆对象受伤HP-10----------- 制造一个分身并放入分身管理器, Name = 深克隆-幻影长矛手_1 -----------对比本体和深克隆体的生命值----------- monkey-HP = 100.0 cloneMonkey-HP = 90.0 销毁全部分身 *************开始测试登记形式的原型模式************* -----------实例化-幻影长矛手,并消耗10MP镜像(深克隆)出分身1----------- 销毁全部分身 制造一个分身并放入分身管理器, Name = 分身1 -----------分身1受到伤害HP-10,并消耗10MP创造出分身1_1----------- 制造一个分身并放入分身管理器, Name = 分身1_1 -----------输出分身管理器----------- Name = 分身1, HP = 90.0, MP = 80.0, ATK = 50.0 , DEF = 50.0 Name = 分身1_1, HP = 90.0, MP = 80.0, ATK = 25.0 , DEF = 25.0 -----------本体使用镜像技能----------- 销毁全部分身 制造一个分身并放入分身管理器, Name = 分身2 -----------再次输出分身管理器----------- Name = 分身2, HP = 100.0, MP = 80.0, ATK = 50.0 , DEF = 50.0 7,总结 原型模式,没有复杂的继承体系 用户不需要了解对象中字段和实例化的相关细节 使用具有拷贝功能的类实现Cloneable接口并重写clone()方法即可 通过clone()方法还能为不同字段设置复制权限,仅对允许被复制的字段进行复制

维护记录:添加幻影长矛手图片



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3