您现在的位置是:首页 > 正文

Java 24 Design Pattern 之 原型模式

2024-01-30 21:04:01阅读 0

原型模式介绍

概述

使用场景

UML类图

原型模式实现

浅拷贝模式

深拷贝模式


原型模式介绍

概述

原型模式顾名思义,就是基于原型来创建对象,用人话说就是一个对象的产生可以不由零起步,直接从一个已经具备一定雏形的对象克隆,然后再修改为所需要的对象。显而易见 ,原型模式属于创建型模式,

使用场景

如果对象的创建成本比较大,例如某个对象里面的数据需要访问数据库才能拿到;并且同一个类的不同对象之间差别不大(大部分字段都相同),这种场景下可以考虑使用原型模式达到提高程序性能的作用。

我们可以通过一个业务场景来理解原型模式的应用,设计一个学生类,学生类主要的成员变量有名字name,班级classId, needExtraCourse,以及所学的课程 course,其中course需要通过rpc调用查询课程系统获取,这个耗时100ms。如果用普通的方式为该类创建同一个班的4个 对象,如下所示:

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

class Student{
    private String name;
    private Integer classId;

    //是否需要额外多选课程 
    private Boolean needExtraCourse = false;
    
    //所学课程,需要通过rpc调用查询课程系统,耗时100ms
    private List<String> course = new ArrayList<>();

    public Student(String name, Integer classId) throws InterruptedException {
        this.name = name;
        this.classId = classId;

        //超时模拟rpc调用获取课程信息初始化course
        Thread.sleep(100);
        course.add("语文");
        course.add("数学");
        course.add("英语");
    }
    
    //getter、setter、toString函数省略
}

public class PrototypeExample {
    public static void main(String[] args) throws InterruptedException {
        long start =  new Date().getTime();
        Student student1 = new Student("张三",  1);
        Student student2 = new Student("李四",  1);
        Student student3 = new Student("王五",  1);
        Student student4 = new Student("赵六",  1);

        long end =  new Date().getTime();
        System.out.println("创建对象共花费了时间:" + (end -start) + " ms");
    }
}

******************【运行结果】******************
创建对象共花费了时间:413 ms

只是创建了4个对象就花费了400多ms,这样成本也太大了,其实对于一个班级的学生,不考虑额外选修课程的情况下(needExtraCourse = false),所学的课程应该是完全一样的。

因此可以用到今天介绍的原型模式:先用普通的方式创建一个对象,然后从创建的对象中克隆出其它对象,再修改其它对象的name字段即可。

UML类图

UML 类图也比较简单,只有两个部分:

  • • 1.Cloneable接口

  • • 2.Student类,实现了Cloneable接口的原型对象,这个对象有个能力就是可以克隆自己。

原型模式实现

原型模式的实现方式有两种:浅拷贝和深拷贝。关于浅拷贝和深拷贝,可以阅读详解浅拷贝与深拷贝

浅拷贝模式

浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。Object类提供的方法clone只是拷贝本对象 , 其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,下面是浅拷贝原型模式实现的代码:

//1原型类需实现Cloneable接口
class Student implements Cloneable{
    //Student类的其余部分和上面例子一样
    ......................

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

public class PrototypeExample {
    public static void main(String[] args) throws InterruptedException, CloneNotSupportedException {
        long start =  new Date().getTime();
        Student student1 = new Student("张三",  1);

        Student student2 = (Student) student1.clone();
        student2.setName("李四");

        Student student3 = (Student) student1.clone();
        student3.setName("王五");

        Student student4 = (Student) student1.clone();
        student4.setName("赵六 ");

        System.out.println("student1--->" + student1);
        System.out.println("student2--->" + student2);
        System.out.println("student3--->" + student3);
        System.out.println("student4--->" + student4);

        long end =  new Date().getTime();
        System.out.println("创建对象共花费了时间:" + (end -start) + " ms");


        //赵六选修了课程美术
        student4.setNeedExtraCourse(true);
        student4.getCourse().add("美术 ");
        System.out.println("\n赵六选修美术课后:");
        System.out.println("student1--->" + student1);
        System.out.println("student2--->" + student2);
        System.out.println("student3--->" + student3);
        System.out.println("student4--->" + student4);
    }
}
******************【运行结果】******************
student1--->Student{name='张三', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
student2--->Student{name='李四', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
student3--->Student{name='王五', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
student4--->Student{name='赵六 ', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
创建对象共花费了时间:130 ms

赵六选修美术课后:
student1--->Student{name='张三', classId=1, needExtraCourse=false, course=[语文, 数学, 英语, 美术 ]}
student2--->Student{name='李四', classId=1, needExtraCourse=false, course=[语文, 数学, 英语, 美术 ]}
student3--->Student{name='王五', classId=1, needExtraCourse=false, course=[语文, 数学, 英语, 美术 ]}
student4--->Student{name='赵六 ', classId=1, needExtraCourse=true, course=[语文, 数学, 英语, 美术 ]}

        可以看到,通过原型模式的方式,同样是创建四个对象,只花了100多ms,大大的提高了程序性能。

        但是这里还存在一个小问题:赵六比较好学,所以还选修了美术,但是通过程序的运行结果可以看到,明明只是赵六选修了美术,但是其他三个同学的课表中也都出现了美术课 。这是因为浅拷贝虽然产生了两个完全不同的对象,但是对象中有对其他对象的引用(如这里的List)都指向同一个对象。

为了解决这个问题,我们引入了深拷贝模式。

深拷贝模式

深拷贝模式把要复制的对象所引用的对象都拷贝了一遍。

class Student implements Cloneable{
    //Student类的其余部分和上面例子一样
    ......................
    @Override
    public Object clone() throws CloneNotSupportedException {
        Object object = super.clone();
        Student student = (Student)object;

        List<String> newCourse = new ArrayList<>();
        Iterator<String>  it = student.course.iterator();

        while (it.hasNext()) {
            newCourse.add(it.next());
        }

        student.course  = newCourse;
        return object;
    }
}

public class PrototypeExample {
    public static void main(String[] args) throws InterruptedException, CloneNotSupportedException {
        long start =  new Date().getTime();
        Student student1 = new Student("张三",  1);

        Student student2 = (Student) student1.clone();
        student2.setName("李四");

        Student student3 = (Student) student1.clone();
        student3.setName("王五");

        Student student4 = (Student) student1.clone();
        student4.setName("赵六 ");

        System.out.println("student1--->" + student1);
        System.out.println("student2--->" + student2);
        System.out.println("student3--->" + student3);
        System.out.println("student4--->" + student4);

        long end =  new Date().getTime();
        System.out.println("创建对象共花费了时间:" + (end -start) + " ms");


        //赵六选修了课程美术
        student4.setNeedExtraCourse(true);
        student4.getCourse().add("美术 ");
        System.out.println("\n赵六选修美术课后:");
        System.out.println("student1--->" + student1);
        System.out.println("student2--->" + student2);
        System.out.println("student3--->" + student3);
        System.out.println("student4--->" + student4);
    }
}

******************【运行结果】******************
student1--->Student{name='张三', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
student2--->Student{name='李四', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
student3--->Student{name='王五', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
student4--->Student{name='赵六 ', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
创建对象共花费了时间:135 ms

赵六选修美术课后:
student1--->Student{name='张三', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
student2--->Student{name='李四', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
student3--->Student{name='王五', classId=1, needExtraCourse=false, course=[语文, 数学, 英语]}
student4--->Student{name='赵六 ', classId=1, needExtraCourse=true, course=[语文, 数学, 英语, 美术 ]}

可以看到,深拷贝模式 ,修改一个对象的引用类型的成员不会再影响另外对象的该成员了。

网站文章

  • java-String类

    java-String类

    StringString:字符串,使用一对“”引起来表示猜猜这个结果是什么?字符串与其他类型的转换String:字符串,使用一对“”引起来表示1.String声明为final的,不可被继承2.Stri...

    2024-01-30 21:03:55
  • RabbitMQ之消息模式1

    RabbitMQ之消息模式1

    1、消息如何保证100%的投递?第一步:保障消息的成功发出第二步:保障MQ节点的成功接收第三步:发送端收到MQ节点(Broker)确认应答第四步:完善的消息进行补偿机制BAT/TMD互联网大厂的解决方案:方法一:消息落库,对消息状态进行打标方法二:消息的延迟投递,做二次确认,回调检查流程步骤:第1步:将订单入库,创建一条MSG(状态为0) 入MSG DB库第2步:将消息发出去...

    2024-01-30 21:03:48
  • C++重载

    目录重载覆盖隐藏重载运算符重载是C++新增的机制,将语义和功能相似的函数用同一个名字表示,提高函数的通用性。重载特征:(1)相同范围(2)函数名相同(3)参数不同(4)virtual可有可无全局函数和...

    2024-01-30 21:03:19
  • ORACLE数据库 —— PL/SQL知识点2

    ORACLE数据库 —— PL/SQL知识点2

    ORACLE数据库 —— PL/SQL知识点2 内置函数 游标

    2024-01-30 21:03:14
  • security的一个过滤器——SecurityContextPersistenceFilter

    security的一个过滤器——SecurityContextPersistenceFilter1、关于security的用户信息获取a、SecurityContextHolder.getContex...

    2024-01-30 21:03:07
  • 聊聊ctrl+c和ctrl+z的区别

    一句话总结:ctrl+c是强制中断程序,ctrl+c是暂停程序。 Ctrl+C Ctrl+Z Ctrl+D bg fg jobs 强制中断程序,进程终止 暂停程序,挂起 退出shell 将一个在后台暂停的命令,变成继续执行 将后台中的命令调至前台继续运行 查看

    2024-01-30 21:02:36
  • 【蓝桥杯】100个数相乘末尾有几个零

    【蓝桥杯】100个数相乘末尾有几个零

    蓝桥杯-简单计算与模拟部分

    2024-01-30 21:02:23
  • 云时代架构读后感(十二)

    途牛订单的服务化演进 原文地址: https://mp.weixin.qq.com/s?__biz=MzI3MzEzMDI1OQ==&amp;mid=2651814702&amp;idx=1&amp;sn=cafc4aa95db9cfdbd0373d00c633a8fb&amp;scene=21#wechat_redirect 一个系统无论视同开发还是运行时的资源,都无法满足业务的需求...

    2024-01-30 21:02:16
  • 数字图像处理_傅里叶变换_输出矩阵的物理含义分析总结

    考虑二维傅里叶变换。傅里叶变换实现了将图像从空间域到频率域(也叫变换域)的转换,这种转换让我们得到了一个关于原图像灰度信息的频谱图,这个频谱图可以看做是图像梯度的分布图(图像梯度是两个点像素灰度的差值...

    2024-01-30 21:01:46
  • BZOJ2127Happiness

    题目描述高一一班的座位表是个n*m的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友。这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值。作为计算机竞赛教练的scp大老板,想知道如何分配可以使得全班的喜悦值总和最大。题解这道题相当于给了我们一堆二元关系。容易想到用二元关系最小割来解决。我们设...

    2024-01-30 21:01:39