设计模式之原型模式:当美猴王拔了一根毫毛

一、原型模式的定义
原型模式是一个非常简单的设计模式,也是一种创建型设计模式。原型模式允许一个对象在创建另一个可定制的对象。根本无需知道任何如何创建的细节。就像孙悟空拔了一根毫毛一吹就招来了一群孙悟空一样。我们都不知道是怎么创建的。

在原型模式中,我们需要定义一个克隆的接口或者包含克隆方法的类,让具体的原型类去实现它,或者重写它。当我们需要创建对象时,我们就可以在client客户端调用具体原型类的克隆方法实现对象的复制创建。以下是原型模式的UML图:
在这里插入图片描述
二、原型模式的使用

2.1 前提知识
在Java语言下、Java提供了一个Cloneable 接口来标识这个对象是允许拷贝的。为什么说是“标识”呢,因为这个接口什么方法也没有,只是提供了一个标示的作用。

public interface Cloneable {
}

那么怎么才能从允许拷贝转换到可以真正的能拷贝呢?方法是我们重写clone()方法。你沒有看错,在Java中每个类的祖宗都是Object类。在Object类中是默认有clone方法的:

 protected native Object clone() throws CloneNotSupportedException;

是不是觉得非常的简单,下面我们通过美猴王的例子实现一下。

2.2快速实现
我们首先定义一个美猴王类MonkeyKing,该类有两个属性:名称和年龄,并实现 Cloneable接口,简单的重写clone方法就可以了:

//美猴王
public class MonkeyKing implements Cloneable{
    //名称
    private String name;
    //年龄
    private int age;
    //构造方法
    public MonkeyKing() {
    }

//重写Object类的clone
    @Override
    protected MonkeyKing clone() {
        MonkeyKing monkeyKing= null;
        try {
            monkeyKing = (MonkeyKing) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return monkeyKing;
    }

客户端(这里在main方法中)使用原型模式创建:

 public static void main(String[] args) {
       //孙悟空本体
        MonkeyKing monkeyKing = new MonkeyKing();

        monkeyKing.setName("孙悟空");
        monkeyKing.setAge(3000);

        int i = 0;
        // 创建猴子的个数
        int COUNT = 100;

        while (i < COUNT){
            MonkeyKing cmonkeyKing = monkeyKing.clone();
            i++;
            System.out.println(cmonkeyKing.toString());
        }
    }

结果:

MonkeyKing{name=‘孙悟空’, age=3000}
MonkeyKing{name=‘孙悟空’, age=3000}
MonkeyKing{name=‘孙悟空’, age=3000}
MonkeyKing{name=‘孙悟空’, age=3000}

通过以上的简单的操作,我们就实现了对象的复制。

三、深拷贝和浅拷贝:原型模式下的金箍棒
我们知道孙悟空是有一个神兵,就是定海神针,不是,是如意金箍棒。孙悟空本身只有一个金箍棒,如果给孙悟空这个对象添加一个兵器的对象。那么,再使用以上的方式进行克隆时候,金箍棒是不是也被同时克隆了呢?我们先代码看看:

我们新建Arms兵器类,属性有名称name和重量weight。

//兵器
public class Arms {

    private  int weight;
    private   String name;
    。。。。
    }

我们让美猴王对象有这么一个兵器:

//每猴王
public class MonkeyKing implements Cloneable {

    //名称
    private String name;
    //年龄
    private int age;
    //兵器
    private Arms arms;

    public MonkeyKing() {
    }

修改之前的客户端,使用同样的方式克隆美猴王对象,并通过对比美猴王对象的hashCode、兵器的hashcode,判断每一个克隆的美猴王对象与本体是否一样,以及兵器是否一样:

public static void main(String[] args) {
        //孙悟空本体
        MonkeyKing monkeyKing = new MonkeyKing();

        monkeyKing.setName("孙悟空");
        monkeyKing.setAge(3000);
        Arms arms = new Arms();
        arms.setName("金箍棒");
        arms.setWeight(180000);
        monkeyKing.setArms(arms);
        int i = 0;
        // 创建猴子的个数
        int COUNT = 100;

        while (i < COUNT) {
            MonkeyKing cmonkeyKing = monkeyKing.clone();
            i++;
            System.out.println(cmonkeyKing.toString());
            int monkeyKinghashCode = cmonkeyKing.hashCode();

            if (monkeyKinghashCode == monkeyKing.hashCode()) {
                System.out.println("克隆的美猴王是一个对象");
            } else {
                System.out.println("克隆的美猴王不是一个对象");
            }
            int hashCode = cmonkeyKing.getArms().hashCode();
            if (hashCode == arms.hashCode()) {
                System.out.println("克隆的兵器是同一个对象");
            } else {
                System.out.println("克隆的兵器不是同一个对象");

            }
        }
    }
}

结果:

MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象

我们可以看到美猴王对象都是新的一个对象,而兵器却都是一个。所有的美猴王持有的兵器的引用都是指向一个兵器对象。由此我们可以下一个结论:通过简单的clone方法,对于数据类型是基本数据类型的成员变量,是直接的复制,而对于引用数据类型,比如成员变量是某一个数组、对象,那么只会进行引用传递,只是将该成员的引用复制给了对象而已。我们称之为:浅拷贝。 当然有浅拷贝就有深拷贝。如何能够实现成员变量是引用数据类型是也能复制实现深拷贝呢?

四、深拷贝的实现方法
4.1 重写克隆方法实现深拷贝
我们首先将兵器类实现Cloneable接口,添加克隆方法

public class Arms implements Cloneable {
    private int weight;
    private String name;
    
    @Override
    protected Arms clone() {
        Arms arms = null;
        try {
            arms = (Arms) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return arms;
    }
    
    ....

再重写美猴王的clone方法

package com.cog.singleton.prototype;

//每猴王
public class MonkeyKing implements Cloneable {

    //名称
    private String name;
    //年龄
    private int age;
    //兵器
    private Arms arms;

    public MonkeyKing() {
    }
    @Override
    protected MonkeyKing clone() {

        MonkeyKing monkeyKing = null;
        try {    
            //实现基本类型的克隆
            monkeyKing = (MonkeyKing) super.clone();
            //引用对象单独的克隆
            monkeyKing.arms = (Arms) monkeyKing.arms.clone();

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return monkeyKing;
    }

这样我们通过客户端再次创建100个美猴王:

public static void main(String[] args) {

        //孙悟空本体
        MonkeyKing monkeyKing = new MonkeyKing();

        monkeyKing.setName("孙悟空");
        monkeyKing.setAge(3000);
        Arms arms = new Arms();
        arms.setName("金箍棒");
        arms.setWeight(180000);
        monkeyKing.setArms(arms);
        int i = 0;
        // 创建猴子的个数
        int COUNT = 100;

        while (i < COUNT) {
            MonkeyKing cmonkeyKing = monkeyKing.clone();
            i++;
            System.out.println(cmonkeyKing.toString());
            int monkeyKinghashCode = cmonkeyKing.hashCode();

            if (monkeyKinghashCode == monkeyKing.hashCode()) {
                System.out.println("克隆的美猴王是一个对象");
            } else {
                System.out.println("克隆的美猴王不是一个对象");

            }
            int hashCode = cmonkeyKing.getArms().hashCode();
            if (hashCode == arms.hashCode()) {
                System.out.println("克隆的兵器是同一个对象");
            } else {
                System.out.println("克隆的兵器不是同一个对象");

            }
        }
    }

结果:

MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象

我们发现兵器也实现了克隆。

4.2 通过序列化的方式实现深拷贝
首先,让美猴王类和兵器类都是实现Serializable接口。接着我们在美猴王类中自己写一个deepClone的方法:

//每猴王
public class MonkeyKing implements Cloneable, Serializable {
    //名称
    private String name;
    //年龄
    private int age;
    //兵器
    private Arms arms;
    public MonkeyKing() {
    }
    @Override
    protected MonkeyKing clone() {

        MonkeyKing monkeyKing = null;
        try {

            //实现基本类型的克隆
            monkeyKing = (MonkeyKing) super.clone();
            //引用对象单独的克隆
            monkeyKing.arms = (Arms) monkeyKing.arms.clone();

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return monkeyKing;
    }

//使用序列化的方法
    public MonkeyKing deepClone() {

        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            MonkeyKing monkeyKing = (MonkeyKing) ois.readObject();
            return monkeyKing;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (Exception e) {

            }
        }
    }

客户端克隆:

public static void main(String[] args) {

        //孙悟空本体
        MonkeyKing monkeyKing = new MonkeyKing();

        monkeyKing.setName("孙悟空");
        monkeyKing.setAge(3000);
        Arms arms = new Arms();
        arms.setName("金箍棒");
        arms.setWeight(180000);
        monkeyKing.setArms(arms);
        int i = 0;
        // 创建猴子的个数
        int COUNT = 100;

        while (i < COUNT) {
           
            MonkeyKing cmonkeyKing = monkeyKing.deepClone();
            i++;
            System.out.println(cmonkeyKing.toString());
            int monkeyKinghashCode = cmonkeyKing.hashCode();

            if (monkeyKinghashCode == monkeyKing.hashCode()) {
                System.out.println("克隆的美猴王是一个对象");
            } else {
                System.out.println("克隆的美猴王不是一个对象");

            }
            int hashCode = cmonkeyKing.getArms().hashCode();
            if (hashCode == arms.hashCode()) {
                System.out.println("克隆的兵器是同一个对象");
            } else {
                System.out.println("克隆的兵器不是同一个对象");

            }
        }
    }

通过以上的写法也可以实现:

MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象

五、小结
原型模式是先生产出一个包含大量共有信息的类,然后通过拷贝生成一个个这样对象。看过黑客帝国的人都知道。反派人物就是一个人通过复制,复制出大量的反派,最后主角也被复制了。今天讲的原型模式就是这样的概念。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值