设计模式 | 原型模式

知识点六: 原型模式


一、概述

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能,它属于23种GOF设计模式的创建型设计模式  ,它提供了一种创建对象的最佳方式。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。它有如下特点:

  1. 由原型对象自身创建目标对象。也就是说,对象创建这一动作发自原型对象本身。
  2. 目标对象是原型对象的一个克隆。也就是说,通过Prototype模式创建的对象,不仅仅与原型对象具有相同的结构,还与原型对象具有相同的值
  3. 根据对象克隆的深度层次不同,可以分为浅度克隆(浅拷贝)和深度克隆(深拷贝)。

简单的说就是:

采用复制原型对象的方法来创建对象实例,创建出来的实例具有与原型一样的数据


二、深拷贝和浅拷贝

浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。

深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。


三、优缺点和使用场景

1、优点

  • 性能提高。
  • 逃避构造函数的约束。

2、缺点

  • 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
  • 必须实现 Cloneable 接口。

3、使用场景

  • 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
  • 性能和安全要求的场景。
  • 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  • 一个对象多个修改者的场景。
  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
  • 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

四、模式中包含的角色和其职责以及模式实现方式

1、角色

抽象原型(Prototype)角色:声明克隆自身的接口,Cloneable接口。

具体原型(ConcretePrototype)角色:实现克隆的具体操作。

2、实现方式

了解Java的同学应该都知道,所有的Java类都继承至Object,而Object类提供了一个clone()方法,该方法可以将一个java对象复制一份,因此在java中可以直接使用clone()方法来复制一个对象。但是需要实现clone的Java类必须要实现一个接口:Cloneable。该接口表示该类能够复制且具体复制的能力,如果不实现该接口而直接调用clone()方法会抛出CloneNotSupportedException异常。

一般而言,clone()方法满足:

  • 对任何的对象obj,都有obj.clone() !=obj,即克隆对象与原对象不是同一个对象。
  • 对任何的对象obj,都有obj.clone().getClass()==obj.getClass(),即克隆对象与原对象的类型一样。
  • 如果对象obj的equals()方法定义恰当,那么obj.clone().equals(obj)应该成立。

原型模式.png


五、在Java中的实现

1.浅度克隆(浅拷贝)

  • 原型类Person.java

    /**
     * @Description: 原型类
     * @Author: Ling.D.S
     * @Date: Created in 2018/11/8 20:25
     */
    public class Person implements Cloneable{
        private String name;
        private int age;
        //朋友(复杂类型)
        private List<String> friends;
      
        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 List<String> getFriends() {
            return friends;
        }
      
        public void setFriends(List<String> friends) {
            this.friends = friends;
        }
      
        public Person clone(){
            try {
                return (Person) super.clone();
            } catch (CloneNotSupportedException e) {
                //抛出一个不支持克隆异常
                e.printStackTrace();
                return null;
            }
        }
    }
    
  • 测试类MainClass.java

    /**
     * @Description:
     * @Author: Ling.D.S
     * @Date: Created in 2018/11/8 20:25
     */
    public class MainClass {
        public static void main(String[] args) {
            //先创建一个人,他有一些朋友
            Person person = new Person();
            person.setName("宋德凌");
            person.setAge(18);
            ArrayList<String> friends = new ArrayList<String>();
            friends.add("彭于晏");
            friends.add("吴彦祖");
            person.setFriends(friends);
      
            //再克隆这个人
            Person person1 = person.clone();
            //比较克隆体和原型的属性
            System.out.println("=======第一次比较=======");
            System.out.println(person.getFriends().toString());
            System.out.println(person1.getFriends().toString());
      
            //这时候原型又交了一个朋友
            friends.add("胡歌");
            person.setFriends(friends);
            //比较克隆体和原型的属性
            System.out.println("=======第二次比较=======");
            System.out.println(person.getFriends().toString());
            System.out.println(person1.getFriends().toString());
        }
    }
    
  • 结果

    浅拷贝结果.png

    导致这样的结果的原因在于,浅拷贝只是传递引用,不能复制实例

2.深度克隆

  • 原型类

    /**
     * @Description: 原型类
     * @Author: Ling.D.S
     * @Date: Created in 2018/11/8 20:25
     */
    public class Person implements Cloneable{
        private String name;
        private int age;
        //朋友(复杂类型)
        private List<String> friends;
      
        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 List<String> getFriends() {
            return friends;
        }
      
        public void setFriends(List<String> friends) {
            this.friends = friends;
        }
      
        public Person clone(){
            try {
                //这个地方还是传递引用,克隆数值型的属性
                Person person = (Person) super.clone();
      
                //核心:为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。
                ArrayList<String> newFriends = new ArrayList<String>();
                for (String friend : this.getFriends()) {
                    newFriends.add(friend);
                }
                person.setFriends(newFriends);
                return person;
            } catch (CloneNotSupportedException e) {
                //抛出一个不支持克隆异常
                e.printStackTrace();
                return null;
            }
        }
    }
    
  • 测试类

    同上

  • 结果

    深拷贝结果.png

注:浅度克隆和深度克隆详解


转载请注明原地址,宋德凌的博客:http://CoderOfSong.github.io 谢谢!

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦