阿粉带你学习设计模式之原型(Prototype)模式

简介: 大家好,我是鸭血粉丝,今天阿粉带大家学习一下设计模式之原型模式,话不多说,我们往下看。今天要跟大家分享的是创建型模式之原型模式,作为设计模式中创建型模式中的一员,很明显原型模式是用来创建对象的,那么到底什么是原型模式,以及原型模式到底是怎么使用的,有哪些使用场景呢?且听阿粉我慢慢道来。

原型模式

首先我们看下原型模式的定义,维基百科上这样解释的:原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的。

阿粉划重点了!

关键词:原型实例,复制,新实例

通俗点说就是我们在某个需要创建实例的地方不是通过 new 关键字来创建某个类,而是通过复制某个类已有的实例来创建一个新的实例。比如有个猴子 Monkey 类,对应有个孙悟空实例我们叫猴哥一号,在打不过某个妖怪 Monster 的时候拔一根猴毛吹一下,克隆一个新的 Monkey,我们叫做猴哥二号,这时有两个猴哥一起对付妖怪。这里我们原始的猴哥一号就是原型对象,猴哥二号就是克隆对象。

这里我们思考这几个点

  1. 是不是每个类都可以克隆?为什么 Monkey 类可以克隆,妖怪 Monster 为什么不克隆呢?
  2. 猴哥二号是不是具备所有猴哥一号的本领呢?要是猴哥二号被妖怪打伤了,猴哥一号会不会受伤呢?

这里阿粉先留下这两个问题,我们继续往下看。

原型模式使用场景

上面提到了原型模式使用场景是在需要创建新的实例的时候不通过 new 而是通过”复制“ 的方式来创建。那么我们会想为什么不通过 new 来创建呢?new 起来多方便啊,搞什么复制那么麻烦,在这里阿粉我摸了摸所剩不多的秀发。74.jpg

那是因为主要有下面两点的原因:

  1. 无法根据类名来 new 对象。我们知道在 new 一个类的时候是需要知道类名的,如果不知道类名叫什么,我们自然无法通过 new 来创建实例;
  2. 需要大量创建相同对象的时候。有时候我们可能在一个循环中创建对象,而且这个对象创建的复杂度很高,那这种时候如果我们每次都是用 new 来创建那么性能会比较低,当然现在 JVM 已经优化的很好了,但是对于有些对性能要求特别高的场景,还是要注意的。

UML 图解

在整个原型模式中我们先了解下大概有几个角色的存在,了解各自的关系,才能更好的方便我们的理解。还是以上面猴哥的例子,首先有个 Monkey 类,而且这个 Monkey 类要支持能克隆。

75.jpg

原型类

76.png

代码演示

  1. 第一步我们先创建Monkey 的原型类,原型类必须实现 Java 的 Cloneable 接口,然后重写父类 Objectclone 方法,如下;
package com.silence.arts.pattern.creator.prototype;
import lombok.Data;
import java.util.ArrayList;
/**
 * <br>
 * <b>Function:</b><br>
 * <b>Desc:</b>Monkey 原型类<br>
 */
@Data
publicclass MonkeyPrototype implements Cloneable {
    private Integer age;
    private String name;
    private ArrayList<String> things;
    private ExtraSkill extraSkill;
    private MonkeyPrototype monkeyPrototype = null;
    @Override
    public MonkeyPrototype clone() {
        try {
            monkeyPrototype = (MonkeyPrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return monkeyPrototype;
    }
}
  1. 我们根据 Monkey 原型,来创建真正的 Mokey 对象,并设置一个输出方法,如下:
package com.silence.arts.pattern.creator.prototype;
/**
 * <br>
 * <b>Function:</b><br>
 * <b>Desc:</b>无<br>
 */
publicclass Monkey extends MonkeyPrototype {
    /**
     * 输出内容
     *
     */
    public void message() {
        System.out.println(this.getName() + "-" + this.getAge() + "-" + this.getThings() + "- 金箍棒长度:" + this.getExtraSkill().getWidth());
    }
}
  1. 由于猴哥还有一些其他本领,我们这里在创建一个其他技能的类
package com.silence.arts.pattern.creator.prototype;
/**
 * <br>
 * <b>Function:</b><br>
 * <b>Desc:</b>无<br>
 */
publicclass ExtraSkill {
    /**
     * 放大金箍棒的宽度
     */
    privateint width;
    /**
     * 放大金箍棒的高度
     */
    privateint height;
    public int getWidth() {
        return width;
    }
    public void setWidth(int width) {
        this.width = width;
    }
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }
}
  1. 创建一个客户端类来使用Monkey
package com.silence.arts.pattern.creator.prototype;
import java.util.ArrayList;
/**
 * <br>
 * <b>Function:</b><br>
 * <b>Desc:</b>无<br>
 */
publicclass MonkeyClient {
    public static void main(String[] args) {
        //创建猴哥一号
        Monkey monkey = new Monkey();
        monkey.setName("猴哥一号");
        monkey.setAge(18);
        ArrayList<String> list = new ArrayList<>();
        list.add("七十二变化");
        list.add("火眼金睛");
        monkey.setThings(list);
        ExtraSkill m = new ExtraSkill();
        m.setWidth(1920);
        m.setHeight(1080);
        monkey.setExtraSkill(m);
        //克隆五个猴哥
        System.out.println("--------------------------克隆猴哥----------------------------");
        for (int i = 0; i < 5; i++) {
            Monkey cloneMonkey = (Monkey) monkey.clone();
            cloneMonkey.setAge(cloneMonkey.getAge() + i);
            cloneMonkey.setName(cloneMonkey.getName() + i);
            ArrayList<String> innerThing = cloneMonkey.getThings();
            innerThing.add("特殊技能 " + i);
            cloneMonkey.setThings(innerThing);
            cloneMonkey.getExtraSkill().setWidth(cloneMonkey.getExtraSkill().getWidth() + i * 100);
            cloneMonkey.getExtraSkill().setHeight(cloneMonkey.getExtraSkill().getHeight() + i * 100);
            System.out.println(cloneMonkey.getExtraSkill());
            cloneMonkey.message();
            System.out.println();
        }
        System.out.println("--------------------------猴哥一号----------------------------");
        System.out.println(monkey.getExtraSkill());
        monkey.message();
    }
}
  1. 运行 MonkeyClient,得到如下输出:
--------------------------克隆猴哥----------------------------
com.silence.arts.pattern.creator.prototype.ExtraSkill@2503dbd3
猴哥一号0-18-[七十二变化, 火眼金睛, 特殊技能 0]- 金箍棒长度:1920
com.silence.arts.pattern.creator.prototype.ExtraSkill@2503dbd3
猴哥一号1-19-[七十二变化, 火眼金睛, 特殊技能 0, 特殊技能 1]- 金箍棒长度:2020
com.silence.arts.pattern.creator.prototype.ExtraSkill@2503dbd3
猴哥一号2-20-[七十二变化, 火眼金睛, 特殊技能 0, 特殊技能 1, 特殊技能 2]- 金箍棒长度:2220
com.silence.arts.pattern.creator.prototype.ExtraSkill@2503dbd3
猴哥一号3-21-[七十二变化, 火眼金睛, 特殊技能 0, 特殊技能 1, 特殊技能 2, 特殊技能 3]- 金箍棒长度:2520
com.silence.arts.pattern.creator.prototype.ExtraSkill@2503dbd3
猴哥一号4-22-[七十二变化, 火眼金睛, 特殊技能 0, 特殊技能 1, 特殊技能 2, 特殊技能 3, 特殊技能 4]- 金箍棒长度:2920
--------------------------猴哥一号----------------------------
com.silence.arts.pattern.creator.prototype.ExtraSkill@2503dbd3
猴哥一号-18-[七十二变化, 火眼金睛, 特殊技能 0, 特殊技能 1, 特殊技能 2, 特殊技能 3, 特殊技能 4]- 金箍棒长度:2920
Process finished with exit code 0
  1. 运行结果分析 上面程序中我们首先通过复杂的流程创建的猴哥一号(这里虽然很简单,但是我们假设很复杂。。。),并且设置了猴哥一号的名称和一些技能,然后通过克隆的方式克隆了五个猴哥,并且我们进行了重新命名,以及技能的调整,最后输出所有猴哥的名称和技能点。
    可以看到我们完美的克隆了五个猴哥,但是仔细观察我们会发现一个问题,在更改了克隆出来的猴哥的名字和年龄的时候对原始的猴哥一号没有影响,但是在更改克隆猴哥的技能的时候,原始猴哥的技能也变掉了!
    这里我们如果认为这些技能是原始猴哥的,那么就可以说克隆出来的猴哥并没有克隆出属于自己的技能。这里就回答了我们上面的问题,克隆猴哥不是具备所有猴哥一号的本领,至于为什么,那是因为在 Java 中有深拷贝和浅拷贝,关于这个,这里就不展开了,留给大家自己思考,自己去尝试写一些浅拷贝和深拷贝的代码。
    那么我们再看看上面第一个提问,为什么只有 Monkey 类能克隆呢?Monster 为什么不克隆呢?到这里我想大家都知道了,那是因为 Monster 类没有实现 Java 中的 Cloneable 接口,那自然是无法克隆的。
    解答了上面遗留的两个问题,阿粉的内心激动不已啊~

76.jpg

原型模式的应用

相信大家在使用 Spring 的时候都有用过 @scope("prototype"),由于 Spring 中默认 Bean 是单例模式的,如果我们需要某个 Bean 在每次请求的时候都创建一个新的,而不是只有一个,那就需要将这个 Bean 的模式改成原型模式,这样在每次使用的使用就会创建一个新的,使用结束后就会销毁。另外原型模式很少会单独使用,很多时候都是结合工厂模式一起使用,所以很多时候我们看到的原型模式并不是本文的案例这么清晰,大家要擦亮眼睛。在 Spring 源码 AbstractBeanFactory 类中的 doGetBean() 方法中会根据 Bean 的类型进行注册。

77.jpg

总结

今天阿粉先从原型模式的定义,再到给个具体的案例来带大家认识了解了原型模式,通过这篇文章希望大家对原型模式有了更深的理解。学习设计模式的学习是个漫长的过程,希望大家坚持初心,我们共同学习进步,欢迎大家一起讨论进步。

最后欢迎大家到我们《Java 极客技术》的知识星球中一起学习,更有其他设计模式相关内容分享和 SpringBoot 系列教学视频免费发放,名额有限,先到先得哦。

相关文章
|
6天前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
|
2月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
2月前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
2月前
|
设计模式 安全 Java
Kotlin - 改良设计模式 - 构建者模式
Kotlin - 改良设计模式 - 构建者模式
|
2月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
47 1
|
3月前
|
设计模式 Java Kotlin
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
本教程详细讲解Kotlin语法,适合希望深入了解Kotlin的开发者。对于快速学习Kotlin语法,推荐查看“简洁”系列教程。本文重点介绍了构建者模式在Kotlin中的应用与改良,包括如何使用具名可选参数简化复杂对象的创建过程,以及如何在初始化代码块中对参数进行约束和校验。
34 3
|
3月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
44 0
|
4月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
4月前
|
设计模式 算法 安全
设计模式——模板模式
模板方法模式、钩子方法、Spring源码AbstractApplicationContext类用到的模板方法
设计模式——模板模式
|
4月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:如何提高代码的可维护性与扩展性在软件开发领域,PHP 是一种广泛使用的服务器端脚本语言。随着项目规模的扩大和复杂性的增加,保持代码的可维护性和可扩展性变得越来越重要。本文将探讨 PHP 中的设计模式,并通过实例展示如何应用这些模式来提高代码质量。
设计模式是经过验证的解决软件设计问题的方法。它们不是具体的代码,而是一种编码和设计经验的总结。在PHP开发中,合理地使用设计模式可以显著提高代码的可维护性、复用性和扩展性。本文将介绍几种常见的设计模式,包括单例模式、工厂模式和观察者模式,并通过具体的例子展示如何在PHP项目中应用这些模式。