zoukankan      html  css  js  c++  java
  • 【设计模式】第五篇:什么是原型模式?浅提浅拷贝和深拷贝

    一 原型模式引入

    原型模式作为创建型模式的最后一种,它并没有涉及到很多的内容,我们来看一下

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

    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 中 合作伙伴 的值,没有受到影响,这也就是我们一开头想要实现的效果了

    二 原型模式理论

    (一) 什么是原型模式

    在一些程序中,或许需要创建大量相同或者相似的对象,在构造函数的执行比较缓慢的时候,多次通过传统的构造函数创建对象,就会复杂且耗资源,同时创建时的细节也一样暴露了出来

    原型模式就可以帮助我们解决这一问题

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

    (二) 结构

    根据结构图简单说一下其中的角色:

    • 抽象原型类(Prototype):规定了具体原型对象必须实现的接口
    • 具体原型类(ConcretePrototype):实现 clone 方法,即一个克隆自身的操作
    • 访问类(Client):使用具体原型类中的 clone 方法克隆自身,从而创建一个新的对象

    (三) 两种模式

    根据上面的例子也可以看出来了,原型模式分为浅克隆和深克隆

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

    (四) 优缺点

    优点:

    • Java 原型模式基于内存二进制流复制,比直接 new 的性能会更好一些

    • 可以利用深克隆保存对象状态,存一份旧的(克隆出来),在对其修改,可以充当一个撤销功能

    缺点:

    • 需要配置 clone 方法,改造时需要对已有类进行修改,违背 “开闭原则”
    • 如果对象间存在多重嵌套引用时,每一层都需要实现克隆
      • 例如上例中,Book 中实现深克隆,Partner 中实现浅克隆,所以要注意深浅克隆运用得当

    结尾

    如果文章中有什么不足,欢迎大家留言交流,感谢朋友们的支持!

    如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号

    在这里的我们素不相识,却都在为了自己的梦而努力 ❤

    一个坚持推送原创开发技术文章的公众号:理想二旬不止

  • 相关阅读:
    视觉(9)争取快点看完基于图论的立体匹配算法
    STL笔记(2) STL之父访谈录
    视觉(7)Computer vision from Wikipedia
    *NIX工具使用(1) Vim notes
    STL笔记(1)map
    AI杂谈(1) 你喜欢ML里的哪些模型?希望ML未来向什么方向发展?
    AI杂谈(3): dodo:人脸识别方法个人见解(zz from prfans)
    AI杂谈(2)请教支持向量机用于图像分类
    视觉(3)blepo
    windows编程(2)深入探讨MFC消息循环和消息泵
  • 原文地址:https://www.cnblogs.com/ideal-20/p/13957004.html
Copyright © 2011-2022 走看看