设计模式(8)——对象创建模式(2)——原型模式

目录

1.基本介绍

2.动机

3.类图

4.案例

5.深拷贝与浅拷贝

5.1 浅拷贝

5.2 深拷贝

6.总结


1.基本介绍

定义:

  • 使用原型实例指定创建对象的种类,然后通过拷贝(深拷贝)这些原型来创建新的对象
  • 当原型对象的结构变化,我们不需要修改使用者的代码,只需要修改原型对象本身,而凡是调用了clone方法创建的对象都会在运行时与它相同

工作原理

  • 通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实现创建,即 对象.clone()

2.动机

  • 在软件系统中,经常面临着“某些结构复杂的对象”的创建工作,由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口
  • 如何应对变化?如何向“客户程序(使用这些对象的程序)”隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?

3.类图

  • Prototype:声明一个克隆自己的接口
  • ConcretePrototype:实现一个克隆自己的操作
  • Client:让一个原型对象克隆自己,从而创建一个新的对象(属性一样)

4.案例

案例:克隆羊问题

现在有一只羊tom,姓名为:tom,年龄为:1,颜色为:白色,请编写程序创建和tom羊属性完全相同的10只羊

传统方式实现:

package cn.cqu.prototype;

public class Sheep {
    private String name;
    private int age;
    private String color;

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}
package cn.cqu.prototype;

import java.util.Calendar;

public class Client {
    public static void main(String[] args) {
        Sheep tom = new Sheep("tom",1,"白色");


        Sheep[] sheeps = new Sheep[10];

        for (int i = 0; i < sheeps.length; i++) {
            sheeps[i] = new Sheep(tom.getName(),tom.getAge(),tom.getColor());
            System.out.println(sheeps[i]);
        }

    }
}

分析问题:

  • 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
  • 总是需要重新初始化对象, 而不是动态地获得对象运行时的状态,不够灵活
  • 解决思路:Java中Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力,即原型模式

原型模式实现:

package cn.cqu.prototype;

public class Sheep implements Cloneable{
    private String name;
    private int age;
    private String color;

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }


    //使用默认的方法完成克隆
    @Override
    protected Object clone() {
        Sheep sheep  = null;

        try {
            sheep = (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sheep;
    }
}
package cn.cqu.prototype;

public class Client {
    public static void main(String[] args) {
        Sheep tom = new Sheep("tom",1,"白色");

        Sheep[] sheeps = new Sheep[10];

        for (int i = 0; i < sheeps.length; i++) {
            sheeps[i] = (Sheep) tom.clone();
            System.out.println(sheeps[i]);
        }
    }
}

分析:

  • 使用了原型模式,程序具有更高的效率和扩展性
  • 比如我们在Sheep中要新增一个属性,clone它的对象不需要修改,就拥有了该新属性

5.深拷贝与浅拷贝

5.1 浅拷贝

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象,如上述案例均为值传递

  • 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象,因此实际上两个对象的该成员变量都指向同一个实例,在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值

  • 上述案例中的克隆羊就是浅拷贝,浅拷贝就是使用默认的clone()方法来实现

5.2 深拷贝

当要拷贝的对象a中有其他对象b时,我们为了在拷贝a对象的同时也对b对象进行拷贝,需要使用深拷贝

  • 首先,复制对象的所有基本数据类型的成员变量值
  • 为所有引用数据类型的成员变量申请存储空间,并赋值每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象,也就是说,对象进行深拷贝要对整个对象进行拷贝

Java中深拷贝的实现方式:

实现方式1:重写clone方法实现深拷贝

package cn.cqu.prototype;

public class DeepCloneTarget implements Cloneable{
    private String name;
    private int age;

    public DeepCloneTarget() {

    }

    public DeepCloneTarget(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    //因为该类的属性都是String,因此我们使用默认的clone方法进行浅拷贝即可
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
package cn.cqu.prototype;

public class DeepProtoType implements Cloneable{
    private String name;
    private DeepCloneTarget deepCloneTarget;

    public DeepProtoType() {

    }

    public DeepProtoType(String name, DeepCloneTarget deepCloneTarget) {
        this.name = name;
        this.deepCloneTarget = deepCloneTarget;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public DeepCloneTarget getDeepCloneTarget() {
        return deepCloneTarget;
    }

    public void setDeepCloneTarget(DeepCloneTarget deepCloneTarget) {
        this.deepCloneTarget = deepCloneTarget;
    }


    //使用深拷贝完成克隆

    @Override
    protected Object clone() throws CloneNotSupportedException {
        DeepProtoType deep = null;
        //1.使用默认的clone方法完成基本数据类型属性和字符串类型属性的克隆
        deep = (DeepProtoType)super.clone();
        //2.对引用类型的属性进行单独处理
        deep.deepCloneTarget = (DeepCloneTarget) deepCloneTarget.clone();

        return deep;
    }
}
package cn.cqu.prototype;

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {

        //创建一个原型对象
        DeepProtoType protoType = new DeepProtoType();
        protoType.setName("原型1");
        protoType.setDeepCloneTarget(new DeepCloneTarget("孙悟空",1));

        //克隆一个新的对象
        DeepProtoType p1 = (DeepProtoType) protoType.clone();

        System.out.println("protoType.name"+protoType.getName()+"\n"+
                "protoType.deepCloneTarget.hashCode ="+protoType.getDeepCloneTarget().hashCode());

        System.out.println("=====================");

        System.out.println("p1.name"+p1.getName()+"\n"+
                "p1.deepCloneTarget.hashCode ="+p1.getDeepCloneTarget().hashCode());
    }
}

实现方式2:通过对象序列化实现深拷贝

package cn.cqu.prototype;

import java.io.*;

public class DeepCloneTarget implements Serializable {
    private String name;
    private int age;

    public DeepCloneTarget() {

    }

    public DeepCloneTarget(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
package cn.cqu.prototype;

import java.io.*;

public class DeepProtoType implements Serializable {
    private String name;
    private DeepCloneTarget deepCloneTarget;

    public DeepProtoType() {

    }

    public DeepProtoType(String name, DeepCloneTarget deepCloneTarget) {
        this.name = name;
        this.deepCloneTarget = deepCloneTarget;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public DeepCloneTarget getDeepCloneTarget() {
        return deepCloneTarget;
    }

    public void setDeepCloneTarget(DeepCloneTarget deepCloneTarget) {
        this.deepCloneTarget = deepCloneTarget;
    }


    //通过对象的序列化实现深拷贝
    public Object 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);
            DeepProtoType deep = (DeepProtoType) ois.readObject();
            return deep;

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }finally {
            //关闭流
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            }catch (Exception e2){
                System.out.println(e2.getMessage());
            }
        }
    }
}
package cn.cqu.prototype;

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {

        //创建一个原型对象
        DeepProtoType protoType = new DeepProtoType();
        protoType.setName("原型1");
        protoType.setDeepCloneTarget(new DeepCloneTarget("孙悟空",1));

        //克隆一个新的对象
        DeepProtoType p1 = (DeepProtoType) protoType.deepClone();

        System.out.println("protoType.name:"+protoType.getName()+"\n"+
                "protoType.deepCloneTarget.hashCode ="+protoType.getDeepCloneTarget().hashCode());

        System.out.println("=====================");

        System.out.println("p1.name:"+p1.getName()+"\n"+
                "p1.deepCloneTarget.hashCode ="+p1.getDeepCloneTarget().hashCode());
    }
}

推荐使用方式2,因为第一种clone的方式,如果类中组合了多个其他类的对象时,我们需要在clone方法中对每个对象进行处理,而第二种方式会一并处理整个this对象

6.总结

  • Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些易变类拥有稳定的接口
  • Prototype模式对于“如何创建易变类的实体对象”采用了“原型克隆”的方法来做,它使得我们可以非常灵活地动态创建“拥有某些稳定接口”的新对象——所需工作仅仅是注册一个新类的对象(即原型),然后再任何需要的地方Clone
  • Prototype模式中的Clone方法可以利用某些框架中的序列化来实现深拷贝(Java),C++中可以通过拷贝构造来实现
  • 创建的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  • 不用重新初始化对象,而是动态地获得对象运行时的状态
  • 如果原始对象发生变化(增加或者减少属性),其他克隆对象的也会发生相应的变化,无需修改代码
  • 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了OCP原则。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值