设计模式-原型模式(Prototype Pattern)

Posted 涂程

tags:

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

定义

了解原型模式前,我们先来了解下Java提供两种克隆方式:

  • 浅拷贝:被克隆对象的所有变量都含有与原来的对象相同的值,而它所有的对其他对象的引用都仍然指向原来的对象。换一种说法就是浅克隆仅仅克隆所考虑的对象,而不克隆它所引用的对象。
  • 深拷贝:被克隆对象的所有变量都含有与原来的对象相同的值,但它所有的对其他对象的引用不再是原有的,而这是指向被复制过的新对象。换言之,深复制把要复制的对象的所有引用的对象都复制了一遍,这种叫做间接复制。

面试题:浅拷贝和深拷贝的区别是什么?

  • 浅拷贝是对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝;(8种基本数据类型byte,char,short,int,long,float,double,boolean)
  • 而深拷贝是对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容。(除了浅拷贝的8中基本数据类型,其他都属于深拷贝,例如数组、对象…)

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

代码示例

1.浅拷贝

public class IdCard {
    private String id;

    public IdCard(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "IdCard{" +
                "id=" + id +
                '}';
    }
}

public class Person implements Cloneable {

    private String name;
    private int age;
    private IdCard idCard;

    public Person() {
    }

    public Person(String name, int age, IdCard idCard) {
        this.name = name;
        this.age = age;
        this.idCard = idCard;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name +
                ", age=" + age +
                ", idCard=" + idCard + 
                ", idCard.hashCode=" + idCard.hashCode() +
                '}';
    }

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

执行结果

public class PersonTest {
    public static void main(String[] args) throws Exception {
        Person person = new Person("张三", 20, new IdCard("10086"));

        Person person1 = (Person) person.clone();
        Person person2 = (Person) person.clone();

        System.out.println(person);
        System.out.println(person1);
        System.out.println(person2);
    }
}

Person{name='张三, age=20, idCard=IdCard{id=10086}, idCard.hashCode=1510467688}
Person{name='张三, age=20, idCard=IdCard{id=10086}, idCard.hashCode=1510467688}
Person{name='张三, age=20, idCard=IdCard{id=10086}, idCard.hashCode=1510467688}

我们发现可以通过实现implements Cloneable来完成浅拷贝,基本变量是值传递克隆,而引用对象IdCard则是引用传递,这不符合我们面向对象思想,每一个Person应该都有一个独立的IdCard,而不是共用一个,而要解决这种问题,我们需要使用深克隆

2.深拷贝(第一种)

//实现Cloneable接口
public class IdCard implements Cloneable {
    private String id;

    public IdCard(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "IdCard{" +
                "id='" + id + 
                '}';
    }

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

public class Person implements Cloneable {
    private String name;
    private int age;
    private IdCard idCard;

    public Person(String name, int age, IdCard idCard) {
        this.name = name;
        this.age = age;
        this.idCard = idCard;
    }

    public IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "Person{" +
                "personHashCode=" + this.hashCode() +
                ", name='" + name + 
                ", age=" + age +
                ", idCard=" + idCard +
                ", idCardHashCode=" + idCard.hashCode() +
                '}';
    }

    //深克隆需要自己手动实现,在对象引用中也要实现clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //完成基本数据类型的拷贝
        //通过new关键字创建的对象是引用类型

        Object person = super.clone();

        //对引用类型单独处理
        Person p = (Person) person;
        IdCard idCard = (IdCard) p.getIdCard().clone(); //实现自己的克隆
        p.setIdCard(idCard);
        return p;
    }
}

执行结果

public class PersonTest implements Serializable {
    public static void main(String[] args) throws Exception {

        Person person = new Person("张三", 20, new IdCard("10086"));

        Person person1 = (Person) person.clone();
        Person person2 = (Person) person.clone();

        System.out.println(person);
        System.out.println(person1);
        System.out.println(person2);

    }
}

Person{personHashCode=1510467688, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=1995265320}
Person{personHashCode=746292446, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=1072591677}
Person{personHashCode=1523554304, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=1175962212}

使用这种深克隆的方式,完美的解决了当数据类型为引用类型时,只是拷贝原引用对象地址而不是一个全新的引用对象的引用,但是这种实现有一个很大的弊端,需要在每一个对象中都实现clone方法,如果类全是你自己写的,那自然没问题,实现一下就行了,不过有点麻烦。但是,如果你引用的是第三方的一个类,无法修改源代码,这种方式,显然就无法实现深克隆了

3.深拷贝(第二种)

//实现Serializable接口
public class IdCard implements Serializable {
    private String id;

    public IdCard(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "IdCard{" +
                "id='" + id + 
                '}';
    }

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

public class Person implements Serializable {
    private String name;
    private int age;
    private IdCard idCard;

    public Person(String name, int age, IdCard idCard) {
        this.name = name;
        this.age = age;
        this.idCard = idCard;
    }

    public IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "Person{" +
                "personHashCode=" + this.hashCode() +
                ", name='" + name + 
                ", age=" + age +
                ", idCard=" + idCard +
                ", idCardHashCode=" + idCard.hashCode() +
                '}';
    }

    //序列化的方式
    public Person deelClone() {
        //创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try {
            //序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return (Person) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                ois.close();
                bis.close();
                oos.close();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果

public class PersonTest {
    public static void main(String[] args) throws Exception {
        //创建一个对象
        Person person = new Person("张三", 20, new IdCard("10086"));

        //克隆两个对象
        Person person1 = (Person) person.deelClone();
        Person person2 = (Person) person.deelClone();
        System.out.println("深拷贝(第二种 实现序列化接口)");
        //打印三人信息
        System.out.println(person);
        System.out.println(person1);
        System.out.println(person2);
    }
}

深拷贝(第二种 实现序列化接口)

Person{personHashCode=2055281021, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=1554547125}
Person{personHashCode=804564176, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=1421795058}
Person{personHashCode=1555009629, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=41359092}

这种方式我们需要手动编写deepClone方法,使用Java流中的序列化与反序列化来实现深克隆,但是这种实现,需要在每一个类中都继承序列化Serializable接口,这种方式,如果你调用的是第三方类,也有可能第三方类上没有实现Serializable序列化接口,但是一般来说,大多都会实现,总的来说,这种比较推荐使用,而且效率也高

4 原型模式总结

原型模式本质上就是对象拷贝。使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率,还有一个特点就是保护性拷贝,如果我们操作时,不会对原有的对象造成影响。
优点: 原型模式是在内存中二进制流的拷贝,要比new一个对象的性能要好,特别是需要生产大量对象时。
缺点: 直接在内存中拷贝,构造函数是不会执行的,这样就减少了约束,既是优点也是缺点,在实际开发当中应注意这个问题。

最后分享一下我整理的一些Android 相关学习知识点,大家可以点击下方小卡片进行查看。

以上是关于设计模式-原型模式(Prototype Pattern)的主要内容,如果未能解决你的问题,请参考以下文章

[工作中的设计模式]原型模式prototype

设计模式之笔记--原型模式(Prototype)

原型模式(Prototype Pattern)

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

原型模式--- prototype

5.设计模式----prototype原型模式