zoukankan      html  css  js  c++  java
  • 设计之禅——原型模式

    前言

    “万事万物皆对象!”,这是面向对象的宗旨,在Java中我们无时无刻不在创建对象,那创建对象有哪些方式呢?可以通过new或者反射,还有就是今天要讲的原型模式。那什么是原型模式,为什么又要通过原型模式去创建对象呢?

    正文

    无论是通过new还是反射,我们都免不了要去手动设置其属性,如果类结构非常复杂,同时又需要创建大量相似的对象时,如果还老老实的去new就非常麻烦易出错了,而原型模式则是使用clone方法直接拷贝一个对象,在Java中实现非常简单,只需要重写Object的clone方法就行了。

    Coding

    首先创建一个People类,每个人都有头和手:

    public class People implements Cloneable {
    
        public String name;
        public Integer age;
        public Head head;
        public Arm[] arms;
    
        public People() {
            this.name = "Jack";
            this.age = 10;
            this.head = new Head();
    
            Arm left = new Arm();
            Arm right = new Arm();
            arms = new Arm[2];
            arms[0] = left;
            arms[1] = right;
        }
    
        /**
         * 重写clone方法,需要注意的是Object中
         */
        @Override
        public People clone() throws CloneNotSupportedException {
            return (People) super.clone();
        }
    }
    
    public class Head implements Cloneable {
    
        double weight = 10;
    
        @Override
        public Head clone() throws CloneNotSupportedException {
            return (Head) super.clone();
        }
    }
    
    public class Arm implements Cloneable {
    
        double length = 5;
    
        @Override
        public Arm clone() throws CloneNotSupportedException {
            return (Arm) super.clone();
        }
    }
    

    这样我们需要创建同样的对象时,只需要调用clone方法就行了,这个被拷贝的对象就被称为原型,不过这两个对象之间有什么异同呢?修改其中一个对象的属性对另外一个对象又有什么影响呢?思考一下下面这段代码的结果:

      People people = new People();
      People clone = people.clone();
    
      System.out.println(people == clone);
      System.out.println(people.name == clone.name);
      System.out.println(people.age == clone.age);
      System.out.println(people.head == clone.head);
      System.out.println(people.arms == clone.arms);
    

    你会发现除了第一个为false以外,其他的全是true,换言之,它们的属性所引用的对象为同一个对象,改变任何一个对象的属性都会影响到另外一个对象的属性,使用下面代码验证一下:

            System.out.println("-------name-------");
            people.name = "Harry";
            System.out.println(people.name);
            System.out.println(clone.name);
    
            System.out.println("--------age------");
            people.age = 555;
            System.out.println(people.age);
            System.out.println(clone.age);
    
            System.out.println("-------head-------");
            System.out.println("改变前people:" + people.head.weight);
            System.out.println("改变前clone:" + clone.head.weight);
            people.head.weight = 55;
            System.out.println("改变后people:" + people.head.weight);
            System.out.println("改变后clone:" + clone.head.weight);
    
            System.out.println("-------arm-------");
            // 先检验数组中对象
            System.out.println("改变前people:" + people.arms[0]);
            System.out.println("改变前clone:" + clone.arms[0]);
            people.arms[0] = null;
            System.out.println("改变后people:" + people.arms[0]);
            System.out.println("改变后clone:" + clone.arms[0]);
    

    打印结果:

    -------name-------
    Harry
    Jack
    --------age------
    555
    1111
    -------head-------
    改变前people:10.0
    改变前clone:10.0
    改变后people:55.0
    改变后clone:55.0
    -------arm-------
    改变前people:cn.dark.Arm@1b6d3586
    改变前clone:cn.dark.Arm@1b6d3586
    改变后people:null
    改变后clone:null
    

    这是怎么回事?head和arm属性确实如我们所想并没有被完全拷贝,但name和age属性却是独立的。

    出现这样的结果主要是因为Java中的clone方法是本地方法实现的一个浅拷贝,除基本类型以外的引用类型只会将引用进行拷贝(基本类型则是拷贝值),因此得到的是同一个对象。

    等等,name(String)和age(Integer)不也是引用类型么?

    你说的没错,但仔细思考一下不难理解,String和Integer都是final修饰的不可变对象,意思对其中任何一个对象修改该值,原先的引用值是不会改变的,只会将一个新的引用赋值给该属性,但另外一个对象的该属性仍然是引用的之前的对象,也就出现了这样的结果。

    那该如何实现深拷贝呢?

    很简单,只需要在clone方法中调用属性的clone方法或是重新new一个即可:

        public People clone() throws CloneNotSupportedException {
            People clone = (People) super.clone();
            clone.head = head.clone();
            clone.arms = arms.clone();
            return clone;
        }
    

    也就是说属性对象也需要重写clone方法,到这我们不难看出原型模式一个致命的缺点:

    如果拷贝的对象层次非常深,那么实现原型拷贝就会非常的复杂。

    因此我们也需要根据具体情况考虑是否使用原型模式。

    等等,还没有完,你应该注意到了上面的类都实现了一个Cloneable接口,这个接口是做什么的呢?
    它是一个标记接口,表示这个类是可以被clone的,如果不实现该接口,那么在调用该类对象的clone方法时就会抛出CloneNotSupportedException异常,在使用时需要注意。

    总结

    原型模式非常的简单,但它有以下三点需要注意:

    • 被克隆的类必须实现Cloneable接口;
    • Java中的clone方法是浅拷贝,在需要完全复制一个全新对象时,需要自己实现。
    • clone是本地方法实现,它直接操作内存当中的二进制流(性能也就远远超过new和反射),不会调用对象的构造方法,意味者若遇到需要控制类构造器的访问权限的情况时(如单例模式),就无法使用原型模式了,它会绕开访问权限控制。
  • 相关阅读:
    jzoj3294. 【SHTSC2013】超级跳马
    jzoj3243. Cube
    jzoj3242. Spacing
    jzoj3232. 【佛山市选2013】排列
    jzoj3297. 【SDOI2013】逃考
    jzoj4800. 【GDOI2017模拟9.24】周末晚会
    学习burnside、polya小结
    学习splay或spaly小结
    一个初学者的辛酸路程-了解Python-2
    一个初学者的辛酸路程-初识Python-1
  • 原文地址:https://www.cnblogs.com/yewy/p/13111840.html
Copyright © 2011-2022 走看看