【设计模式】第八篇 - 原型模式 - DOTA- 幻影长矛手
发布于: 刚刚
一,前言
上一篇说了建造者模式,这篇说创建型设计模式的最后一个:原型模式
实际开发中,有时需要为一个类创建多个实例,而有些类的实例化开销较大且耗时
所以我们希望通过复制已创建的对象来创建相同或相似的对象,以减少实例化开销
原型模式就是从一个对象创建另一个新的对象,使新的对象有具有原对象的特征的设计模式
在进行原型模式之前,我们需要先了解一下原型模式所涉及到的其他知识点
二,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英雄-暗影长矛手(以下简称猴子),作为原型模式Demo
DOTA游戏中,猴子的主要技能就是镜像(以下称分身),为了说明问题,我们把技能简化一下:
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.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
复制代码
七,总结
原型模式,没有复杂的继承体系
用户不需要了解对象中字段和实例化的相关细节
使用具有拷贝功能的类实现Cloneable接口并重写clone()方法即可
通过clone()方法还能为不同字段设置复制权限,仅对允许被复制的字段进行复制
复制代码
划线
评论
复制
发布于: 刚刚阅读数: 2
Brave
关注
还未添加个人签名 2018.12.13 加入
还未添加个人简介
评论