设计模式之原型模式

咱们在建立对象时,一般是经过new关键字来建立的。可是,思考一下,若是当前类的构造函数很复杂,每次new对象时都会消耗很是多的资源,这样确定是不行的,耗时又费力。ide

那有没有什么办法解决这种问题呢?固然有,原型模式就能够解决这个痛点。函数

原型模式很是好理解,就是类的实例对象能够克隆自身,产生新的实例对象,这样就无需用new来建立。想一下,齐天大圣孙悟空是否是拔一根汗毛,就复制出了不少个如出一辙的孙悟空,道理是同样的。(新的对象和原对象,内容相同,可是内存地址不一样,由于是不一样的对象。)测试

那在Java中,咱们怎么实现原型模式呢?很是简单,只须要原型对象实现Cloneable接口就能够了,看代码:(学生对象的复制,学生对象中包含他所学的学科类的对象)this

//学科类
public class Subject {
    private String name;
    private String content;

    public String getName() {
        return name;
    }

    public Subject setName(String name) {
        this.name = name;
        return this;
    }

    public String getContent() {
        return content;
    }

    public Subject setContent(String content) {
        this.content = content;
        return this;
    }

    public Subject(String name, String content) {
        this.name = name;
        this.content = content;
    }

    public Subject() {
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}
//学生类
public class Student implements Cloneable {
    private int age;
    private String name;
    private Subject subject;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public Student(int age, String name, Subject subject) {
        this.age = age;
        this.name = name;
        this.subject = subject;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", subject=" + subject +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //实现Cloneable接口,调用父类Object的clone方法来实现对象的拷贝
        return super.clone();
    }
}
public class ProTest {
    public static void main(String[] args) throws Exception {
        Student s1 = new Student(18,"张三",new Subject("语文","这是语文书"));
        //经过调用s1对象的clone方法,便可建立一个新的对象s2
        Student s2 = (Student)s1.clone();

        System.out.println(s1);
        System.out.println(s2);

        s2.setAge(20);
        s2.setName("李四");
        s2.getSubject().setName("数学").setContent("这是数学书");

        System.out.println("=======");
        System.out.println(s1);
        System.out.println(s2);

    }
}

打印结果以下:.net

Student{age=18, name='张三', subject=Subject{name='语文', content='这是语文书'}}
Student{age=18, name='张三', subject=Subject{name='语文', content='这是语文书'}}
========
Student{age=18, name='张三', subject=Subject{name='数学', content='这是数学书'}}
Student{age=20, name='李四', subject=Subject{name='数学', content='这是数学书'}}

能够发现,s2对象修改的年龄和姓名对原对象s1没有任何影响,可是subject对象修改以后,原对象s1中的subject对象内容也被更改了,这是怎么回事呢?其实,这是由于咱们使用的是浅拷贝。(Object对象的clone方法自己就是浅拷贝)code

浅拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量只进行引用的复制,而不复制引用所指向的对象。(嗯?这句话怎么听起来这么熟悉,看下这个:为何你们都说Java中只有值传递)此时,新对象里边的引用类型变量至关于原对象的引用类型变量的副本,他们指向的是同一个对象。对象

所以,修改了s2对象的subject对象的内容,原对象s1的subject对象内容也会跟着改变。那若是,我不想让原对象的引用类型变量内容发生改变,应该怎么作呢?blog

这就要用到深拷贝了,即把引用类型变量的内容也拷贝一份,这样他们就互不影响了。通常有两种方式来实现深拷贝:一种是让须要拷贝的引用类型也实现Cloneable接口,而后重写clone方法;另外一种是利用序列化。接口

clone方式:内存

仍是以上边的例子来讲。首先须要修改Subject类让它实现Cloneable接口,重写clone方法。而后修改Student类的clone方法,把全部引用类型的变量手动拷贝一下。

public class Subject implements Cloneable{
    private String name;
    private String content;

    public String getName() {
        return name;
    }

    public Subject setName(String name) {
        this.name = name;
        return this;
    }

    public String getContent() {
        return content;
    }

    public Subject setContent(String content) {
        this.content = content;
        return this;
    }

    public Subject(String name, String content) {
        this.name = name;
        this.content = content;
    }

    public Subject() {
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }

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

public class Student implements Cloneable {
    private int age;
    private String name;
    private Subject subject;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public Student(int age, String name, Subject subject) {
        this.age = age;
        this.name = name;
        this.subject = subject;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", subject=" + subject +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student cloneStu = (Student)super.clone();
        //手动拷贝subject对象,而后赋值给student克隆的新对象
        cloneStu.setSubject((Subject) this.subject.clone());
        return cloneStu;
    }
}

再次运行测试类,会发现修改新对象的引用类型变量已经没法影响原对象的引用类型变量了。

Student{age=18, name='张三', subject=Subject{name='语文', content='这是语文书'}}
Student{age=18, name='张三', subject=Subject{name='语文', content='这是语文书'}}
========
Student{age=18, name='张三', subject=Subject{name='语文', content='这是语文书'}}
Student{age=20, name='李四', subject=Subject{name='数学', content='这是数学书'}}

序列化方式

能够发现,用clone的方式实现起来是很是简单的。可是,考虑若是对象中的引用类型变量不少的时候,这种方式就不太方便 了,由于你要把全部的引用类型都手动clone一遍。另外,若是引用类型又嵌套了多层引用类型,那将是一场灾难。这时,就能够考虑用序列化方式。

系列化方式是经过把对象序列化成二进制流,放到内存中,而后再反序列化成为新的Java对象,这样就能够保证新对象和原对象的互相独立了。

这种方式,须要全部类都实现Serializable接口。Student类只须要添加一个系列化和反序列化的方法就能够了。

public class Subject implements Serializable {
    private String name;
    private String content;

    public String getName() {
        return name;
    }

    public Subject setName(String name) {
        this.name = name;
        return this;
    }

    public String getContent() {
        return content;
    }

    public Subject setContent(String content) {
        this.content = content;
        return this;
    }

    public Subject(String name, String content) {
        this.name = name;
        this.content = content;
    }

    public Subject() {
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}
public class Student implements Serializable {
    private int age;
    private String name;
    private Subject subject;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public Student(int age, String name, Subject subject) {
        this.age = age;
        this.name = name;
        this.subject = subject;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", subject=" + subject +
                '}';
    }


    //深克隆
    public Object deepClone() throws IOException, ClassNotFoundException{
        //把对象写入到流中
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //从流中读取
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    }
}
public class ProTest {
    public static void main(String[] args) throws Exception {
        Student s1 = new Student(18,"张三",new Subject("语文","这是语文书"));
        Student s2 = (Student)s1.deepClone();

        System.out.println(s1);
        System.out.println(s2);

        s2.setAge(20);
        s2.setName("李四");
        s2.getSubject().setName("数学").setContent("这是数学书");

        System.out.println("========");
        System.out.println(s1);
        System.out.println(s2);

    }
}

能够看到,在测试类中经过调用deepClone自定义的深克隆方法便可。这种方式适合引用类型或者子嵌套特别多的状况,每一个类只须要实现Serializable接口便可,Student的deepClone方法也不须要再改动。

须要注意,这种方式的深拷贝,类中引用类型变量不能用 transient 修饰。(不懂的,自行搜索transient关键字,简单说就是用transient修饰的变量默认不会被序列化)

相关文章
相关标签/搜索