Java设计模式学习记录-原型模式

前言

最近一直在面试,也没时间写博客了,感受已经积攒了好多知识想要记录下来了,由于在面试中遇到的没答出来的问题,这就是本身不足的地方,而后就要去学习这部份内容,虽说本身不足的地方学习了,可是没有应用到具体实际的地方,过段时间也仍是会忘,因此个人办法是用博客记录下来。java

俗话说“好记性不如烂笔头”,在我这里是“好记性不如烂博客”😂。面试

今天要介绍的原型模式也是建立型模式中的一种,感受叫复制方法模式或许更接地气一些,个人理解就是用一个对象复制出另外一对象。例如《西游记》中孙悟空拔几根猴毛就能变出好几个同样的孙猴子来。其中孙悟空就是一个原型,建立孙猴子的过程就是实现原型模式的过程。编程

原型模式

原型模式介绍

原型模式是指使用原型实例来指定建立对象的种类,而且经过拷贝这些原型建立新的对象。数组

在使用原型模式时,咱们须要首先建立一个原型对象,再经过复制这个原型对象,来建立更多的同类型的对象。编程语言

如何实现复制

原型模式中究竟是如何实现复制的呢?下面介绍两种实现方式。ide

一、通用的方式

通用的方式是在具体的原型类的复制方法中,实例化一个与自身类型同样的对象,传入相同的属性值,而后将其返回。学习

以下代码方式:测试

public class PrototypeTest {

    //属性变量
    private String name;

    public String getName() {
        return name;
    }

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

    /**
     * 复制方法
     * @return
     */
    protected PrototypeTest clone()  {

        PrototypeTest prototypeTest = new PrototypeTest();
        prototypeTest.setName(name);
        return prototypeTest;
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        PrototypeTest prototypeTest = new PrototypeTest();
        prototypeTest.setName("第三");
    //复制原型 PrototypeTest cloneObject
= prototypeTest.clone(); System.out.println(Objects.toString(prototypeTest)); System.out.println(Objects.toString(cloneObject)); } }

输出的结果是:this

PrototypeTest(name=第三)
PrototypeTest(name=第三)

这种方式通用性很高,而且与编程语言特性无关,任何一种面向对象的语言均可以使用这种形式来实现对原型的复制。spa

二、Java中的Object的clone()方法

由于在Java中全部的Java类都继承自java.lang.Object。而Object的类中提供一个默认的clone()方法,能够将一个Java对象复制一份。所以在Java中能够直接使用Object提供的clone()方法来实现对象的复制,这样实现原型模式就比较简单了。

须要注意的是,可以调用clone()实现拷贝的Java类,必须实现一个标识接口Cloneable,表示这个Java类支持被复制,为何说是标识接口呢,由于这个接口里面没有定义任何方法,只是用了标识能够执行某些操做。若是一个类没有实现这个接口可是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedExecption异常。

以下代码方式:

public class PrototypeMain implements Cloneable{

    private String name;
    private int age;

    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;
    }

    /**
     * 重写Object的clone方法
     * @return
     */
    @Override
    protected PrototypeMain clone() {
        PrototypeMain prototypeMain = null;
        try {
             prototypeMain = (PrototypeMain)super.clone();
        }catch (CloneNotSupportedException e){
            System.err.println("Not Support Cloneable");
        }
        return prototypeMain;
    }

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

    //测试
    public static void main(String[] args) {
        PrototypeMain prototypeMain = new PrototypeMain();
        prototypeMain.setName("小花");
        prototypeMain.setAge(19);
        PrototypeMain cloneObject = prototypeMain.clone();

        System.out.println(Objects.toString(cloneObject));
    }
}

运行结果:

PrototypeMain{name='小花', age=19}

此时Object类能够理解为抽象原型类,而实现了Cloneable接口的类至关于具体原型类。

经过复制方法所建立的对象是全新的对象,它们在内存中拥有全新的地址,一般对复制所产生的对象进行修改时,对原型对象不会形成任何影响,每个拷贝对象都是相互独立的。经过不一样的方式修改,能够获得一系列类似但不彻底相同的对象。

原型模式的结构以下图:

在原型模式结构图中包含以下3个角色。

Prototype(抽象原型类):这是声明复制方法的接口,是全部具体原型类的公共父类,能够是抽象类,也能够是接口,甚至能够是实现类。在上面介绍的实现复制的第二种方法里面的java.lang.Object类就是担当的这个角色。

ConcretePrototype(具体原型类):实现抽象原型类中声明的复制方法,在复制方法中返回一个与本身同类的复制对象。在上面介绍的实现复制的第二种方法里面的PrototypeMain类就是担当的这个角色。

Client(客户类):让一个原型对象复制自身,从而建立一个新的的对象。在客户类中只须要直接实例化或经过工厂方法等方式建立一个原型对象,再经过调用该对象的复制方法,就能够获得多个相同的对象了。在上面介绍的实现复制的第二种方法里面,我将main方法写在了具体原型类中,若是将main方法提出到一个新的的使用类中,那么这个使用类就是客户类。

深Copy与浅Copy

浅Copy是指被复制的对象的全部变量都含有与原来的对象相同的值,而全部的对其余对象的引用都指向原来的对象。简单点说就是,只复制了引用,而没有复制真正的对象内容。

深Copy是指被复制的对象的全部变量都含有与原来对象相同的值,属性中的对象都指向被复制过的新对象中属性,而再也不是原型对象中的属性。简单点说,就是深Copy把全部的对象的引用以及对象都复制了一遍,在堆中是存在两个相互独立的对象,以及属性中的对象也是相互独立的。

咱们仍是举例来讲明吧:

以下代码,建立一个原型类。

public class ShallowCopy implements Cloneable {
    //对象属性
    private ArrayList<String> nameList = new ArrayList<>();

    /**
     * 复制方法
     * @return
     */
    @Override
    protected ShallowCopy clone() {

        ShallowCopy shallowCopy = null;
        try{
            shallowCopy = (ShallowCopy)super.clone();

        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return shallowCopy;
    }

    /**
     * 得到属性
     * @return
     */
    public ArrayList<String> getNameList() {
        return nameList;
    }

    /**
     * 填充属性值
     * @param name
     */
    public void setNameList(String name) {
        this.nameList.add(name);
    }
}

在客户类种使用,进行复制。

public class ClientTest {

    @Test
    public void test(){

        //建立一个对象
        ShallowCopy shallowCopy = new ShallowCopy();
        shallowCopy.setNameList("小红");

        //复制一个新对象
        ShallowCopy newObject = shallowCopy.clone();
        //给新对象的属性赋值
        newObject.setNameList("大黄");

        System.out.println(shallowCopy.getNameList());
    }

}

预想的结果应该是:小红,实际输出:

[小红, 大黄]

产生这种结果的缘由是由于Object类的clone()方法致使的,clone()方法在复制对象时,只是复制本对象的引用,对其内部的数组、引用对象等都不复制,仍是指向原生对象的内部元素地址,这种复制方式就是浅Copy。在实际项目中使用这种方式的仍是比较少的。通常内部的数组和引用对象才不复制,其余的原始类型int、long、double等类型是会被复制的。另外String类型也是会被复制的,String类里是没有clone()的。

那么如何实现深Copy呢?

将上面的复制方法的代码改造一下:

   /**
     * 复制方法
     * @return
     */
    @Override
    protected ShallowCopy clone() {

        ShallowCopy shallowCopy = null;
        try{
            shallowCopy = (ShallowCopy)super.clone();
            shallowCopy.nameList = (ArrayList<String>) this.nameList.clone();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return shallowCopy;
    }

其余内容不变,获得的输出结果是:

[小红]

经过上述改造,咱们实现了深Copy,这样复制出来的新对象和原型对象之间没有任何瓜葛了。实现了互相操做互不影响的效果,其实深Copy还有一种实现方式,那就是经过本身来写二进制流来操做对象,而后实现对象的深Copy。

使用二进制流实现深Copy

将上面的深Copy代码进行改造,改造后的代码以下:

public class ShallowCopy implements Serializable{
    //对象属性
    private ArrayList<String> nameList = new ArrayList<>();

    /**
     * 复制方法
     * @return
     */
    @Override
    protected ShallowCopy clone() {

        ShallowCopy shallowCopy = null;

        try{
            //写入当前对象的二进制流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            //读出二进制流产生新的对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);

            shallowCopy = (ShallowCopy)ois.readObject();

        }catch (IOException|ClassNotFoundException e){
            e.printStackTrace();
        }


        return shallowCopy;
    }

    /**
     * 得到属性
     * @return
     */
    public ArrayList<String> getNameList() {
        return nameList;
    }

    /**
     * 填充属性值
     * @param name
     */
    public void setNameList(String name) {
        this.nameList.add(name);
    }
}

客户使用类内容不变。运行结果以下:

[小红]

须要注意的是经过这种方式来进行深Copy时,原型类必须实现Serializable接口,这样才能将执行序列化将对象转为二进制数据。

深Copy还有另外一点须要注意的是,若是原型类中的属性是一个引用类型的对象,这个属性是不能用final修饰的,若是被final修饰后会编译出错。final修饰的属性是不容许被从新赋值的。因此要使用深Copy时,在成员属性上不要使用final.

相关文章
相关标签/搜索