乐趣区

关于设计模式:设计模式第五篇什么是原型模式浅提浅拷贝和深拷贝

一 原型模式引入

原型模式作为创立型模式的最初一种,它并没有波及到很多的内容,咱们来看一下

首先举一个生存上的例子,例如咱们要出版一本书,其中有一些信息字段,例如书名价格等等

public class Book {
    private String name; // 姓名
    private int price; // 价格
    private Partner partner; // 合作伙伴
    // 省略构造函数、get set、toString 等
}

援用类型 Partner 也很简略

public class Partner{
    private String name;
    
// 省略构造函数、get set、toString 等
}

(一) 间接 new

书籍出版必定不能只出一本,如何大批量生产呢?

有人或者想到,像上面这样每一次都从新 new(甚至写个 for 循环),咱先不说大量 new 的效率问题,首先每次一都须要从新给新对象复制,这不就像,我每刊印一本书,就得从新写一次吗???

public class Test {public static void main(String[] args) throws CloneNotSupportedException {
        // 初始化一个合作伙伴类型
        Partner partner = new Partner("张三");
        // 带参赋值
        Book bookA = new Book("现实二旬不止", 66, partner);
        Book bookB = new Book("现实二旬不止", 66, partner);

        System.out.println("A:" + bookA.toString());
        System.out.println("A:" + bookA.hashCode());
        System.out.println("B:" + bookB.toString());
          System.out.println("B:" + bookB.hashCode());

    }
}

有的同学还或者想到了,先把 A 创立进去,而后再赋值给 B、C ….. 等等,然而这种形式其实是传递援用而不是传值,这就好比在 C 和 B 上写着,内容详情请看 A

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

        // 初始化一个合作伙伴类型
        Partner partner = new Partner("张三");
        // 带参赋值
        Book bookA = new Book("现实二旬不止", 66, partner);

        System.out.println("A:" + bookA.toString());
        System.out.println("A:" + bookA.hashCode());
        
        // 援用赋值
        Book bookB = bookA;
        
        System.out.println("B:" + bookB.toString());
        System.out.println("B:" + bookB.hashCode());
    }
}

这两样显然是不行的,咱们失常的思路是,作者只须要写一次书籍内容,先刊印一本,如果能行,就照着这个样本进行大批量同彩复印,而下面的传援用办法也显然不适合,这就须要用到 Java 克隆

(二) 浅克隆

用到克隆,首先就对 Book 类进行解决

  • 首先实现 Cloneable 接口
  • 接着重写 clone 办法
public class Book implements Cloneable{
    private String name; // 姓名
    private int price; // 价格
    private Partner partner; // 合作伙伴

    @Override
    protected Object clone() throws CloneNotSupportedException {return super.clone();
    }
    
    // 省略构造函数、get set、toString 等
}

再来测试一下

public class Test {public static void main(String[] args) throws CloneNotSupportedException {
        // 初始化一个合作伙伴类型
        Partner partner = new Partner("张三");
        // 带参赋值
        Book bookA = new Book("现实二旬不止", 66, partner);
        // B 克隆 A
        Book bookB = (Book) bookA.clone();

        System.out.println("A:" + bookA.toString());
        System.out.println("A:" + bookA.hashCode());
        System.out.println("B:" + bookB.toString());
        System.out.println("B:" + bookB.hashCode());
    }
}

执行后果

A: Book{name=’ 现实二旬不止 ’, price=66, partner=Partner{name= 张三}}
A: 460141958
B: Book{name=’ 现实二旬不止 ’, price=66, partner=Partner{name= 张三}}
B: 1163157884

后果非常明显,书籍信息是统一的,然而内存地址是不一样的,也就是说的确克隆胜利了,打印其 hashCode 发现两者并不相同,阐明不止指向同一个,也是满足咱们要求的

到这里并没有完结,你会发现还是有问题,当你刊印的过程中批改一些值的内容的时候,你看看成果

public class Test {public static void main(String[] args) throws CloneNotSupportedException {
        // 初始化一个合作伙伴类型
        Partner partner = new Partner("张三");
        // 带参赋值
        Book bookA = new Book("现实二旬不止", 66, partner);
        // B 克隆 A
        Book bookB = (Book) bookA.clone();
        // 批改数据
        partner.setName("李四");

        System.out.println("A:" + bookA.toString());
        System.out.println("A:" + bookA.hashCode());
        System.out.println("B:" + bookB.toString());
        System.out.println("B:" + bookB.hashCode());
    }
}

执行后果

A: Book{name=’ 现实二旬不止 ’, price=66, partner=Partner{name= 李四}}
A: 460141958
B: Book{name=’ 现实二旬不止 ’, price=66, partner=Partner{name= 李四}}
B: 1163157884

???这不对啊,B 明明是先克隆 A 的,为什么我在克隆后,批改了 A 中一个的值,然而 B 也变动了啊

这就是典型的浅克隆,在 Book 类,当字段是援用类型,例如 Partner 这个合作伙伴类,就是咱们自定义的类,这种状况复制援用不赋值援用的对象,因而,原始对象和复制后的这个 Partner 对象是援用同一个对象的

(三) 深克隆

如何解决下面的问题呢,咱们须要从新重写 clone 的内容,同时在援用类型中也实现浅克隆

(1) 被援用类型实现浅克隆

全代码如下

public class Partner implements Cloneable {
    private String name;

    @Override
    protected Object clone() throws CloneNotSupportedException {return super.clone();
    }
    // 省略构造函数、get set、toString 等
}

(2) 批改援用类 cloen 办法

public class Book implements Cloneable{
    private String name; // 姓名
    private int price; // 价格
    private Partner partner; // 合作伙伴

    @Override
    protected Object clone() throws CloneNotSupportedException {Object clone = super.clone();
        Book book = (Book) clone;
        book.partner =(Partner) this.partner.clone();
        return clone;
    }
    // 省略构造函数、get set、toString 等
}

测试一下

public class Test {public static void main(String[] args) throws CloneNotSupportedException {
        // 初始化一个合作伙伴类型
        Partner partner = new Partner("张三");
        // 带参赋值
        Book bookA = new Book("现实二旬不止", 66, partner);
        // B 克隆 A
        Book bookB = (Book) bookA.clone();
        // 批改数据
        partner.setName("李四");

        System.out.println("A:" + bookA.toString());
        System.out.println("A:" + bookA.hashCode());
        System.out.println("B:" + bookB.toString());
        System.out.println("B:" + bookB.hashCode());
    }
}

执行成果

A: Book{name=’ 现实二旬不止 ’, price=66, partner=Partner{name= 李四}}
A: 460141958
B: Book{name=’ 现实二旬不止 ’, price=66, partner=Partner{name= 张三}}
B: 1163157884

能够看到,B 克隆 A 后,批改 A 中 合作伙伴 的值,没有受到影响,这也就是咱们一结尾想要实现的成果了

<div align=”center”>

<img src="//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e0ceb7c9c9f147d58b48cc48f30661f6~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:50%">

</div>

二 原型模式实践

(一) 什么是原型模式

在一些程序中,或者须要创立大量雷同或者类似的对象,在构造函数的执行比拟迟缓的时候,屡次通过传统的构造函数创建对象,就会简单且耗资源,同时创立时的细节也一样裸露了进去

原型模式就能够帮忙咱们解决这一问题

定义:原型模式,甩原型实例指定创建对象的品种,并且通过拷贝这些原型创立新的对象

(二) 构造

依据结构图简略说一下其中的角色:

  • 形象原型类(Prototype):规定了具体原型对象必须实现的接口
  • 具体原型类(ConcretePrototype):实现 clone 办法,即一个克隆本身的操作
  • 拜访类(Client):应用具体原型类中的 clone 办法克隆本身,从而创立一个新的对象

(三) 两种模式

依据下面的例子也能够看进去了,原型模式分为浅克隆和深克隆

  • 浅克隆:创立一个新对象,新对象的属性和原来对象完全相同,对于非根本类型属性,仍指向原有属性所指向的对象的内存地址
  • 深克隆:创立一个新对象,属性中援用的其余对象也会被克隆,不再指向原有对象地址。

(四) 优缺点

长处:

  • Java 原型模式基于内存二进制流复制,比间接 new 的性能会更好一些
  • 能够利用深克隆保留对象状态,存一份旧的(克隆进去),在对其批改,能够充当一个撤销性能

毛病:

  • 须要配置 clone 办法,革新时须要对已有类进行批改,违反“开闭准则”
  • 如果对象间存在多重嵌套援用时,每一层都须要实现克隆

    • 例如上例中,Book 中实现深克隆,Partner 中实现浅克隆,所以要留神深浅克隆使用切当
退出移动版