从零开始学习Java设计模式 | 创建型模式篇:原型模式

Posted 李阿昀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始学习Java设计模式 | 创建型模式篇:原型模式相关的知识,希望对你有一定的参考价值。

在本讲,我们来学习一下创建型模式里面的第四个设计模式,即原型模式。

概述

原型模式就是指用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

这段话读起来有点绕,是不是?没有关系,这里我会举二个例子来给大家解释一下。

第一个例子,大家还记得很早以前的一个新闻——克隆羊吧!如果我们要去克隆羊,首先得有一个真实存在的羊,是不是啊,那么真实存在的这个羊就是原型对象,而根据这个原型对象克隆出来和它一模一样的克隆羊就是新对象。

第二个例子,喜欢看电影的朋友,经常会看到电影里面有这样的一些场景,偷天大盗去偷盗一件宝物时,总是会提前打造一件宝物的复制品(或者仿制品),以到达一个偷天换日、神不知鬼不觉的目的。如果大盗要去仿制宝物的话,首先得有一个真品,然后他再根据真品去做一个和真品一模一样的仿制品,那么在这种情景下,原型对象就是真品,复制出来的新对象就是仿制品。

结构

原型模式包含有如下角色:

  • 抽象原型类:规定了具体原型对象必须实现的clone方法,该clone方法就是克隆的意思
  • 具体原型类:实现抽象原型类中的clone方法,它是可被复制(克隆)的对象
  • 访问类:使用具体原型类中的clone方法来复制(克隆)新的对象

知道原型模式里面包含的具体角色之后,咱们来看一下下面这个类图。

在这里插入图片描述

从以上类图中可以看到,有一个Prototype接口,它就属于抽象原型类,而且它里面还定义有一个clone方法,通过该方法复制出来的就是Prototype接口类型的对象。此外,我们还能看到Prototype接口有一个子实现类,即Realizetype,它就属于具体原型类,从上能看到该子实现类重写了父接口里面的clone方法。最后,我们来看一下访问类,即PrototypeTest,它就是一个测试类,里面有个主方法,主方法里面的测试代码是这样写的:首先创建一个原型对象,因为我们是要根据这个原型对象去创建(或者复制)一个新的对象的,然后调用该原型对象里面的clone方法复制出来一个和原型对象一模一样的新对象。

实现

下面我们就要开始编写代码来实现以上类图所表示的案例了。不过在这之前,大家还得清楚如下概念。

原型模式的克隆分为浅克隆和深克隆:

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

大家初次看到浅克隆和深克隆这两个概念,想必理解的不是很深刻,但是没关系,下面我会通过代码来给大家演示一下。

注意,现在我们所讲述的原型模式指的就是浅克隆。至于深克隆,我等下会给大家进行一个扩展。

此外,我们还应知道一点,在Java里面,Object类提供了clone方法来实现浅克隆,注意是浅克隆哟!而且,在Java里面还有一个接口,即Cloneable,你可以把它理解成原型模式中的抽象原型类,这样,实现了该接口的子实现类就是具体原型类了。所以,等会我们编写代码时,就将以上类图中的Prototype接口换成Cloneable接口,这样做还是有好处的,因为Cloneable接口在Java里面已经定义好了,我们直接拿过来用就行了,而不需要再重复的去定义了。

清楚以上概念之后,接下来,我们就要开始编写代码来实现了。

首先,打开咱们的maven工程,在com.meimeixia.pattern包下新建一个子包,即prototype.demo,使用原型模式实现以上类图所表示的案例的代码我们都放在了该包下。

然后,创建具体原型类,即Realizetype,注意,该类得去实现Cloneable接口并重写它里面的clone方法。

package com.meimeixia.pattern.prototype.demo;

/**
 * @author liayun
 * @create 2021-06-02 6:15
 */
public class Realizetype implements Cloneable {
    public Realizetype() {
        System.out.println("具体的原型对象创建完成!");
    }

    /**
     *
     * @return 克隆出来的,我们明确肯定是该具体原型类的对象,所以我们应把clone方法的返回值类型改成Realizetype
     * @throws CloneNotSupportedException
     */
    @Override
    public Realizetype clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功!");
        return (Realizetype) super.clone();
    }
}

接着,创建访问类,这里我们不妨将其命名为Client。根据以上类图,相信你能写出下面这样的测试代码。

package com.meimeixia.pattern.prototype.demo;

/**
 * @author liayun
 * @create 2021-06-02 6:30
 */
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建一个原型类对象
        Realizetype realizetype = new Realizetype();
        // 调用原型类(即Realizetype)中的clone方法进行对象的克隆
        Realizetype clone = realizetype.clone();
        System.out.println("原型对象和克隆出来的是否是同一个对象呢?" + (realizetype == clone));
    }
}

最后运行以上测试类看结果,如下图所示,打印结果是false,即原型对象和克隆出来的对象不是同一个对象,而且你还能从中发现先是调用了Realizetype类的无参构造方法去创建对象,再是去调用clone方法复制对象,大家注意看哟,在复制对象时,有没有再去执行无参构造方法啊?没有,这说明底层不是通过new对象的方式去克隆一个新对象的,而是通过调用clone方法。

在这里插入图片描述

至此,我们就通过以上代码实现了克隆这样一个效果。

案例

接下来,我们通过一个原型模式的案例再来理解一下原型模式以及原型模式里面的浅克隆和深克隆这俩概念。

该案例就是使用原型模式生成三好学生奖状。

同一学校的"三好学生"奖状除了获奖人姓名不同之外,其他的都相同,那么这种情况下,我们就可以使用原型模式复制多个"三好学生"奖状出来,然后再修改奖状上的名字即可。根据分析,我们是不难画出下面这样的类图的。

在这里插入图片描述

从以上类图中可以看到,首先有一个抽象原型接口,即Cloneable,该接口并不需要我们去定义,因为在Java里面已经定义好了,我们直接用就可以。该接口的子实现类,即Citation,就是具体原型类,能看到我们还在该类中声明了一个name属性来记录获奖人的姓名,并且为其提供了相应的getter和setter方法,最后还重写了父接口中的clone方法。

大家不要忘了还有一个访问类哟,也就是测试类,测试类中只有一个主方法,主方法里面的测试代码是这样写的:首先创建一个原型对象,然后再调用它里面的clone方法克隆出来多个三好学生奖状。

分析至此,接下来,我们就要开始编写代码来实现以上案例了。

首先,在com.meimeixia.pattern.prototype包下新建一个子包,即test,使用原型模式实现以上案例的代码我们都放在了该包下。

首先,创建三好学生类,即Citation,注意,该类得去实现Cloneable接口并重写它里面的clone方法。

package com.meimeixia.pattern.prototype.test;

/**
 * @author liayun
 * @create 2021-06-02 6:43
 */
public class Citation implements Cloneable {

    // 三好学生上的姓名
    private String name;

    public String getName() {
        return name;
    }

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

    /**
     *
     * @return 因为克隆出来的肯定是三号学生类的对象,所以我们要将clone方法的返回值类型修改为Citation
     * @throws CloneNotSupportedException
     */
    @Override
    public Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }

    public void show() {
        System.out.println(name + "同学:在2021学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }

}

大家注意了,我们在以上类中还提供了一个show方法,这在类图中是没画出来的,之所以提供该方法,是因为等会我们在打印结果时,可以让大家看得更加清楚一点。

然后,创建访问类,这里我们就命名为CitationTest了。像下面这样的测试代码不难写吧!

package com.meimeixia.pattern.prototype.test;

/**
 * @author liayun
 * @create 2021-06-02 6:55
 */
public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建原型对象
        Citation citation = new Citation();
        citation.setName("张三"); // 假设这张奖状是张三的

        // 2. 克隆奖状对象
        Citation citation1 = citation.clone();
        citation1.setName("李四"); // 克隆出来的这张奖状是李四的

        // 3. 调用show方法展示奖状
        citation.show();
        citation1.show();
    }
}

最后,我们便要来运行以上访问类看结果了,如下图所示,第一个奖状是张三的,第二个奖状是李四的,打印的结果确实是符合我们的预期。

在这里插入图片描述

使用场景

我们能在哪些场景下使用原型模式呢?这里,我就直接告诉大家答案了,原型模式的使用场景如下:

  • 如果对象的创建非常复杂,那么可以使用原型模式快捷的创建对象,即使用原型对象进行克隆,这样,我们就不需要去关注对象创建的一些细节了。当然,前提是原型对象所属类必须实现Cloneable接口
  • 性能和安全要求比较高。在这种场景下,我们在设计类时不妨让它去实现Cloneable接口,如有需要,则让它去复制或者克隆新的对象,而不是再去new了。而且,在复制对象的过程中,还能按照我们自己的逻辑去创建对应的对象

以上是关于从零开始学习Java设计模式 | 创建型模式篇:原型模式的主要内容,如果未能解决你的问题,请参考以下文章

从零开始学习Java设计模式 | 创建型模式篇:原型模式

从零开始学习Java设计模式 | 创建型模式篇:原型模式

从零开始学习Java设计模式 | 创建型模式篇:抽象工厂模式

从零开始学习Java设计模式 | 创建型模式篇:抽象工厂模式

从零开始学习Java设计模式 | 创建型模式篇:工厂方法模式

从零开始学习Java设计模式 | 创建型模式篇:工厂方法模式