8,原型模式-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)

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()方法

下面我们用一个例子说明原型模式,深复制,浅复制和复制深度的问题

8,原型模式-DOTA-幻影长矛手_第1张图片

我们选择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()方法还能为不同字段设置复制权限,仅对允许被复制的字段进行复制

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

你可能感兴趣的:(GOF_23种设计模式,Java设计模式)