写点什么

【设计模式】第八篇 - 原型模式 - DOTA- 幻影长矛手

用户头像
Brave
关注
发布于: 刚刚

一,前言

上一篇说了建造者模式,这篇说创建型设计模式的最后一个:原型模式

实际开发中,有时需要为一个类创建多个实例,而有些类的实例化开销较大且耗时

所以我们希望通过复制已创建的对象来创建相同或相似的对象,以减少实例化开销

原型模式就是从一个对象创建另一个新的对象,使新的对象有具有原对象的特征的设计模式

在进行原型模式之前,我们需要先了解一下原型模式所涉及到的其他知识点



二,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)
复制代码



四,利用序列化实现深度克隆

对于多层依赖的对象,要想实现深复制,可以使用序列化实现深度克隆读入当前对象的二进制输入,再写出二进制数据对应的对象。
把对象写到流里的过程是序列化(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而不予复制;或者由程序自行创建出相当的同种对象,当做复制件使用。
复制代码



五,原型模式的应用场景分析

实现原型模式的基础就是clone,所以原型模式也需要满足两点:    1,实现Cloneable接口    2,重写Object类的clone()方法
下面我们用一个例子说明原型模式,深复制,浅复制和复制深度的问题
复制代码



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



六,原型模式

一切都准备就绪了,我们来看代码
复制代码


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<String, Prototype> map = new HashMap<String, Prototype>();
/** * 私有化构造方法,避免外部创建实例 */ 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.0cloneMonkey-HP = 90.0
*************开始测试深克隆*************-----------实例化-幻影长矛手,并深克隆-----------销毁全部分身制造一个分身并放入分身管理器, Name = 深克隆-幻影长矛手-----------对比状态对象(引用对象类型)-----------对比引用类型monkey-status 和 cloneMonkey-status : false-----------深克隆对象受伤HP-10-----------制造一个分身并放入分身管理器, Name = 深克隆-幻影长矛手_1-----------对比本体和深克隆体的生命值-----------monkey-HP = 100.0cloneMonkey-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.0Name = 分身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
复制代码



七,总结

原型模式,没有复杂的继承体系用户不需要了解对象中字段和实例化的相关细节使用具有拷贝功能的类实现Cloneable接口并重写clone()方法即可通过clone()方法还能为不同字段设置复制权限,仅对允许被复制的字段进行复制
复制代码


用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
【设计模式】第八篇 - 原型模式 - DOTA-幻影长矛手