最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

8,原型模式

运维笔记admin47浏览0评论

8,原型模式

8,原型模式

一,前言

上一篇说了建造者模式,这篇说创建型设计模式的最后一个:原型模式实际开发中,有时需要为一个类创建多个实例,而有些类的实例化开销较大且耗时
所以我们希望通过复制已创建的对象来创建相同或相似的对象,以减少实例化开销原型模式就是从一个对象创建另一个新的对象,使新的对象有具有原对象的特征的设计模式
在进行原型模式之前,我们需要先了解一下原型模式所涉及到的其他知识点

二,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,英雄本体初始生命值,魔法值,攻击力,防御力均为1003,英雄本体使用技能,魔法值消耗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方法可以随意命名* 浅拷贝只复制基础数据类型,引用类型和原对象指向同一引用*/@Overridepublic 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@Overridepublic 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个镜像@Overridepublic Prototype mirror(String name) throws IOException, ClassNotFoundException {// 如果是本体使用镜像技能先清除全部分身if(!isMirror()){PrototypeManager.clearPrototype();}// 消耗MPStatus 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;}@Overridepublic String getName() {return name;}@Overridepublic void setName(String name) {this.name = name;}@Overridepublic Status getStatus() {return status;}@Overridepublic void setStatus(Status status) {this.status = status;}@Overridepublic boolean isMirror() {return isMirror;}@Overridepublic 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

7,总结

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

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

发布评论

评论列表(0)

  1. 暂无评论