【设计模式】原来这就是原型模式

码农有道公众号

共 12016字,需浏览 25分钟

 ·

2022-07-09 02:57

原型模式介绍

概述

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

使用场景

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

我们可以通过一个业务场景来理解原型模式的应用,设计一个学生类,学生类主要的成员变量有名字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=[语文, 数学, 英语, 
美术 ]}

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

本文源码地址:
https://github.com/qinlizhong1/javaStudy/tree/master/DesignPattern/src/prototype

本文示例代码环境:
操作系统:macOs 12.1
JDK版本:12.0.1
maven版本: 3.8.4

—  —

欢迎关注↓↓↓
发文不易,如有帮助,辛苦点赞和在看
浏览 30
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报