002-创建型-05-原型模式(Protype)

Posted bjlhx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了002-创建型-05-原型模式(Protype)相关的知识,希望对你有一定的参考价值。

一、概述

  指原型实例指定创建对象的种类,并通过克隆这些原型创建新的对象

  原型模式就是让类实现Cloneable接口,达到克隆原型类的方式。

1.1、适用场景

  1、在创建对象的时候,我们不只是希望被创建的对象继承其基类的基本结构,还希望继承原型对象的数据。

  2、希望对目标对象的修改不影响既有的原型对象(深度克隆的时候可以完全互不影响)。

  3、隐藏克隆操作的细节,很多时候,对对象本身的克隆需要涉及到类本身的数据细节。

  4、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等;

  5、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式;

  6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式先产生出一个包含大量共有信息的类,然后可以拷贝出副本,修正细节信息,建立了一个完整的个性对象。

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

1.2、优缺点

优点:

  1. 当创建对象的实例较为复杂的时候,使用原型模式可以简化对象的创建过程,通过复制一个已有的实例可以提高实例的创建效率。
  2. 扩展性好。由于原型模式提供了抽象原型类,在客户端针对抽象原型类进行编程,而将具体原型类写到配置文件中,增减或减少产品对原有系统都没有影响。
  3. 可以使用深克隆方式保存对象的状态。使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

缺点:

  1. 需要为每一个类配置一个克隆方法,而且该克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了“开闭原则”。
  2. 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。

1.3、深浅克隆

  浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

  深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

  总之深浅克隆都会在堆中新分配一块区域,区别在于对象属性引用的对象是否需要进行克隆(递归性的)。

1.4、示例

1.4.1、浅克隆

public class PersonShallow implements Cloneable 
    private String name;
    private Integer age;
    private Date birthday;

    public PersonShallow() 
        System.out.println("ctor PersonShallow");
    

    public String getName()         return name;    

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

    public Integer getAge()         return age;    

    public void setAge(Integer age)         this.age = age;    

    public Date getBirthday()         return birthday;    

    public void setBirthday(Date birthday)         this.birthday = birthday;    

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

    @Override
    public String toString() 
        return "PersonShallow" +
                "name=‘" + name + ‘\‘‘ +
                ", age=" + age +
                ", birthday=" + birthday +
                ‘‘;
    

 

 

测试

    @Test
    public void test() throws Exception 
        //原型A对象
        PersonShallow a = new PersonShallow();
        a.setName("李宏旭");
        a.setAge(1);
        a.setBirthday(new Date());
        System.out.println("a:" + a);
        System.out.println("*************克隆**************");

        //克隆B对象
        PersonShallow b = (PersonShallow) a.clone();
        System.out.println("b:" + b);
        System.out.println("***************比较***************");
        /*
         * 测试A==B?
         * 对任何的对象x,都有x.clone() !=x,即克隆对象与原对象不是同一个对象
         */
        System.out.print("比较:A==B?");
        System.out.println(a == b?"是一个对象":"不是一个对象");
        System.out.println("a.hashCode:"+a.hashCode());
        System.out.println("b.hashCode:"+b.hashCode());

        b.getBirthday().setTime(666666666666L);
        System.out.println("a Birthday为:" + a.getBirthday());
        System.out.println("b Birthday为:" + b.getBirthday());


        a.setName("李四");
        System.out.println("a name 为:" + a.getName());
        System.out.println("b name 为:" + b.getName());

        /*
         * 比较Date对象
         */
        System.out.print("比较:A.Date==B.Date?");
        System.out.println(a.getBirthday() == b.getBirthday()?"是一个对象":"不是一个");
    

 

 

 

 

输出

ctor PersonShallow
a:PersonShallowname=‘李宏旭‘, age=1, birthday=Tue Jul 09 17:25:43 CST 2019
*************克隆**************
b:PersonShallowname=‘李宏旭‘, age=1, birthday=Tue Jul 09 17:25:43 CST 2019
***************比较***************
比较:A==B?不是一个对象
a.hashCode:1811075214
b.hashCode:1588970020
a Birthday为:Sat Feb 16 09:11:06 CST 1991
b Birthday为:Sat Feb 16 09:11:06 CST 1991
a name 为:李四
b name 为:李宏旭
比较:A.Date==B.Date?是一个对象

 

可以发现B在克隆A的birthday时,是直接克隆的引用。这种是浅克隆。

说明点:

  1、(PersonShallow) a.clone();这样子克隆并不等同于Person p2 = p1;像Person p2 = p1;指的是在栈中创建一个变量p2,将p1的内存地址赋给p2,其实指的是同一个对象。而克隆是复制出一份一模一样的对象,两个对象内存地址不同,但对象中的结构与属性值一模一样。

  2、对象克隆拷贝时,类的构造函数是不会被执行的

  3、当被克隆的类中有引用对象(String或Integer等包装类型除外)时,克隆出来的类中的引用变量存储的还是之前的内存地址如Date,也就是说克隆与被克隆的对象是同一个。这样的话两个对象共享了一个私有变量,所有人都可以改,是一个种非常不安全的方式,在实际项目中使用还是比较少的。

1.4.2、深拷贝

示例

public class PersonDeep implements Cloneable 
    private String name;
    private Date birthday;
    private List<String> list;

    public String getName() 
        return name;
    

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

    public Date getBirthday() 
        return birthday;
    

    public void setBirthday(Date birthday) 
        this.birthday = birthday;
    

    public List<String> getList() 
        return list;
    

    public void setList(List<String> list) 
        this.list = list;
    

    @Override
    protected Object clone() throws CloneNotSupportedException 
        PersonDeep deep = null;
        try 
            deep = (PersonDeep) super.clone();
            // 需要特殊处理引用类型的变量 
            deep.birthday = (Date) deep.birthday.clone();
            List<String> newList = new ArrayList();
            if(this.list!=null)
            for (String str : this.list) 
                newList.add(str);
            
            deep.setList(newList);
         catch (CloneNotSupportedException e) 
            e.printStackTrace();
        
        return deep;
    


    @Override
    public String toString() 
        return "PersonDeep" +
                "name=‘" + name + ‘\‘‘ +
                ", birthday=" + birthday +
                ", list=" + list +
                ‘‘;
    

 

测试

    @Test
    public void test() throws Exception 
        //原型A对象
        PersonDeep a = new PersonDeep();
        a.setName("李宏旭");
        a.setBirthday(new Date());
        System.out.println("a:" + a);
        System.out.println("*************克隆**************");

        //克隆B对象
        PersonDeep b = (PersonDeep) a.clone();
        System.out.println("b:" + b);
        System.out.println("***************比较***************");
        /*
         * 测试A==B?
         * 对任何的对象x,都有x.clone() !=x,即克隆对象与原对象不是同一个对象
         */
        System.out.print("比较:A==B?");
        System.out.println(a == b?"是一个对象":"不是一个对象");
        System.out.println("a.hashCode:"+a.hashCode());
        System.out.println("b.hashCode:"+b.hashCode());

        b.getBirthday().setTime(666666666666L);
        System.out.println("a Birthday为:" + a.getBirthday());
        System.out.println("b Birthday为:" + b.getBirthday());


        a.setName("李四");
        System.out.println("a name 为:" + a.getName());
        System.out.println("b name 为:" + b.getName());

        /*
         * 比较Date对象
         */
        System.out.print("比较:A.Date==B.Date?");
        System.out.println(a.getBirthday() == b.getBirthday()?"是一个对象":"不是一个");
    

 

输出

a:PersonDeepname=‘李宏旭‘, birthday=Tue Jul 09 18:03:23 CST 2019, list=null
*************克隆**************
b:PersonDeepname=‘李宏旭‘, birthday=Tue Jul 09 18:03:23 CST 2019, list=[]
***************比较***************
比较:A==B?不是一个对象
a.hashCode:1811075214
b.hashCode:1588970020
a Birthday为:Tue Jul 09 18:03:23 CST 2019
b Birthday为:Sat Feb 16 09:11:06 CST 1991
a name 为:李四
b name 为:李宏旭
比较:A.Date==B.Date?不是一个

  这样就完成了深度拷贝,两种对象互为独立,属于单独对象。

  注意:final 类型修饰的成员变量不能进行深度拷贝 

二、扩展

2.1、JDK1.8源码中的原型模式

ArrayList的clone()
HashMap的clone()
CacheKey的clone()

 

 

 

 

防守打法

以上是关于002-创建型-05-原型模式(Protype)的主要内容,如果未能解决你的问题,请参考以下文章

浅析设计模式——创建型模式之Prototype(原型模式)

05 创建型原型模式 理解克隆对象~

Java23种设计模式之创建型模式「原型模式」

创建者模式之原型模式

一天一个设计模式——Prototype 原型模式

巧记设计模式