原型模式详解附有代码案例分析(浅克隆和深克隆的相关解析)

一、原型模式的概念与角色

(一)、原型模式的概念

​ 原型模式是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,属于创建型模式。

​ 原型模式的核心在于拷贝原型对象。以系统中已存在的一个对象为原型,直接基于内存二进制流进行拷贝,无需经历耗时的对象初始化过程(不调用构造函数),性能提升了许多。当对象的构建过程比较耗时时,可以利用当前系统中已存在的对象作为原型,对其进行克隆(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大减少。

对不通过new关键字,而是通过对象拷贝来实现创建对象的模式就称作原型模式。

(二)、原型模式的角色

  • 客户(Client):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。

  • 抽象原型(Prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。

  • 具体原型(Concrete Prototype):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。

二、原型模式的应用场景

1、类初始化消耗资源较多

2、new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)

3、构造函数比较复杂

4、循环体中生产大量对象时

三、原型模式的通用写法

抽象原型接口:Prototype

public interface Prototype<T> {
    
    
    T clone();
}

具体原型:ConcretePrototype

public class ConcretrPrototype implements Prototype {
    
    
    private int age;
    private String name;

    public int getAge() {
    
    
        return age;
    }

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

    public String getName() {
    
    
        return name;
    }

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

    @Override
    public ConcretrPrototype clone() {
    
    
        ConcretrPrototype concretrPrototype = new ConcretrPrototype();
        concretrPrototype.setAge(this.age);
        concretrPrototype.setName(this.name);
        return concretrPrototype;
    }

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

具体调用:Main

public class Main {
    
    
    public static void main(String[] args) {
    
    
        ConcretrPrototype prototype = new ConcretrPrototype();
        prototype.setAge(18);
        prototype.setName("Tom");
        System.out.println(prototype);
        ConcretrPrototype cloneType = prototype.clone();
        System.out.println(cloneType);
    }
}

四、原型模式——使用Java语言提供的clone()方法

(一)、Java语言提供的clone()方法

​ 众所周知,所有的Java类都继承自 java.lang.Object。事实上,Object 类提供一个 clone() 方法,可以将一个Java对象复制一份。因此在Java中可以直接使用 Object 提供的 clone() 方法来实现对象的克隆,Java语言中的原型模式实现很简单。

​ 需要注意的是能够实现克隆的Java类必须实现一个 标识接口 Cloneable,表示这个Java类支持被复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个 CloneNotSupportedException 异常。

(二)、通用写法的改进——代码示例

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

    public int getAge() {
    
    
        return age;
    }

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

    public String getName() {
    
    
        return name;
    }

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

    @Override
    public ConcretrPrototype clone() {
    
    
        try {
    
    
            return (ConcretrPrototype) super.clone();
        } catch (CloneNotSupportedException e) {
    
    
            e.printStackTrace();
        }
    }

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

五、浅克隆与深克隆

(一)、浅克隆与深克隆的特点

1、浅克隆的特点

(1)、在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

(2)、简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制

(3)、在Java语言中,通过覆盖Object类clone()方法可以实现浅克隆。

2、深克隆的特点

(1)、在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

(2)、简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

(3)、在Java语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

(二)、浅克隆的代码示例

具体原型:ConcretePrototype

增加属性:List hobbies

public class ConcretrPrototype implements Cloneable {
    
    
    private int age;
    private String name;
    private List<String> hobbies;

    public int getAge() {
    
    
        return age;
    }

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

    public String getName() {
    
    
        return name;
    }

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

    @Override
    public ConcretrPrototype clone() {
    
    
        try {
    
    
            return (ConcretrPrototype) super.clone();
        } catch (CloneNotSupportedException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }

    public List<String> getHobbies() {
    
    
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
    
    
        this.hobbies = hobbies;
    }

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

具体调用:Main

public class Main {
    
    
    public static void main(String[] args) {
    
    
        ConcretrPrototype prototype = new ConcretrPrototype();
        prototype.setAge(18);
        prototype.setName("Tom");

        List<String> hobbies = new ArrayList<>();
        hobbies.add("游戏");
        hobbies.add("小说");
        prototype.setHobbies(hobbies);

        // 拷贝原型对象
        ConcretrPrototype cloneType = prototype.clone();
        cloneType.getHobbies().add("睡觉");

        System.out.println("原型对象:" + prototype);
        System.out.println("拷贝对象:" + cloneType);
    }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5xc3ADtP-1600863898092)(C:\Users\huang\AppData\Roaming\Typora\typora-user-images\image-20200923173010505.png)]

(三)、深克隆的代码示例

具体原型:ConcretePrototype

改写clone()方法

public class ConcretrPrototype implements Cloneable, Serializable {
    
    
    private int age;
    private String name;
    private List<String> hobbies;

    public int getAge() {
    
    
        return age;
    }

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

    public String getName() {
    
    
        return name;
    }

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

    public ConcretrPrototype deepClone() {
    
    
        try {
    
    
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);

            return (ConcretrPrototype) ois.readObject();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }

    public List<String> getHobbies() {
    
    
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
    
    
        this.hobbies = hobbies;
    }

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

具体调用:Main

public class Main {
    
    
    public static void main(String[] args) {
    
    
        ConcretrPrototype prototype = new ConcretrPrototype();
        prototype.setAge(18);
        prototype.setName("Tom");

        List<String> hobbies = new ArrayList<>();
        hobbies.add("游戏");
        hobbies.add("小说");
        prototype.setHobbies(hobbies);

        // 拷贝原型对象
        ConcretrPrototype cloneType = prototype.deepClone();
        cloneType.getHobbies().add("睡觉");

        System.out.println("原型对象:" + prototype);
        System.out.println("拷贝对象:" + cloneType);
        System.out.println(prototype == cloneType);

        System.out.println("原型对象的爱好:" + prototype.getHobbies());
        System.out.println("拷贝对象的爱好:" + cloneType.getHobbies());
        System.out.println(prototype.getHobbies() == cloneType.getHobbies());
    }
}

运行结果:

在这里插入图片描述

六、深克隆对单例模式的破坏

(一)、饿汉式单例模式

public class HungrySingleton implements Serializable, Cloneable {
    
    
    private final static HungrySingleton hungrySingleton;

    static {
    
    
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton() {
    
    
        if (hungrySingleton != null) {
    
    
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

    public static HungrySingleton getInstance() {
    
    
        return hungrySingleton;
    }

    private Object readResolve() {
    
    
        return hungrySingleton;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        return super.clone();
    }
}

(二)、测试代码

public class Test {
    
    
    public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    
    
        HungrySingleton hungrySingleton = HungrySingleton.getInstance();
        Method method = hungrySingleton.getClass().getDeclaredMethod("clone");
        method.setAccessible(true);
        HungrySingleton cloneHungrySingleton = (HungrySingleton) method.invoke(hungrySingleton);
        System.out.println(hungrySingleton);
        System.out.println(cloneHungrySingleton);
    }
}

(三)、结果显示

​ 由图可知,该单例模式被破坏,生成的是两个不同的对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uIxC6wpc-1600863898116)(C:\Users\huang\AppData\Roaming\Typora\typora-user-images\image-20200923201827217.png)]

(四)、解决方法

​ 如果我们克隆的目标的对象是单例对象,那意味着,深克隆就会破坏单例。实际上防止克隆破坏单例只需要禁止深克隆便可。要么单例类不实现Cloneable接口;要么重写clone()方法,在clone()方法中返回单例对象即可。

    @Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        return hungrySingleton;
    }

七、原型模式的优缺点

(一)、优点

1、性能优良,Java自带的原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多

2、可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可以辅助实现撤销操作。

(二)、缺点

1、需要为每一个类配置一个克隆方法

2、克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则。

3、在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深拷贝、浅拷贝需要运用得当。

八、设计模式的相关博客文章链接

1、七大设计原则的简单解释(包含合成复用原则),简单理解、快速入门,具备案例代码

链接: 七大设计原则的简单解释(包含合成复用原则),简单理解、快速入门,具备案例代码.

2、工厂模式详解附有代码案例分析(简单工厂,工厂方法,抽象工厂)

链接: 工厂模式详解附有代码案例分析(简单工厂,工厂方法,抽象工厂).

3、单例模式详解及代码案例与应用场景(饿汉式单例模式、懒汉式单例模式、注册式单例模式)

链接: 单例模式详解及代码案例与应用场景(饿汉式单例模式、懒汉式单例模式、注册式单例模式).

4、建造者模式详解附有代码案例分析(包含建造者模式与工厂模式的区别分析)

链接: 建造者模式详解附有代码案例分析(包含建造者模式与工厂模式的区别分析).

猜你喜欢

转载自blog.csdn.net/hyyyya/article/details/108760856