设计模式-创建型模式:原型模式

简介: 设计模式-创建型模式:原型模式

1、简介

在软件工程中,原型(Prototype)模式是一种创建型模式,它允许用户复制一个已经存在的对象,而不必通过构造函数创建它们。它可以在运行时创建对象的副本,这样就不必在每次需要对象时都使用构造函数。

2、组成部分

原型模式可以分为三个部分:

①Prototype: 声明一个克隆自身的接口。

②ConcretePrototype: 实现接口 (—>具体的"原型":如何克隆)

③Client: 让一个原型克隆自身从而创建一个新对象。

原型模式是一种浅复制,也就是说如果原始对象中存在引用类型的属性,那么克隆出来的对象的引用类型属性也会指向原始对象的引用类型属性所指向的对象。如果需要深复制,需要对引用类型属性进行复制。

3、浅复制

浅复制是指在复制一个对象时,只复制它的基本类型属性值,而不复制它的引用类型属性。浅复制得到的新对象与原始对象的引用类型属性指向同一个对象。

例如:在Java中, 使用Object的clone()方法来复制对象, 如果这个对象中有其他对象类型的属性, 使用clone() 得到的副本会和原始对象共用这些对象类型的属性。

浅复制常常被用来复制基本类型和不可变类型的对象,因为它能够满足要求,而且比较简单,性能也比较高。

需要注意的是, 浅复制得到的新对象与原始对象的引用类型属性指向同一个对象, 如果新对象对这些对象类型的属性进行修改,也会影响到原始对象, 所以浅复制不能保证对象的独立性.

4、深复制

深复制是指在复制一个对象时,不仅复制它的基本类型属性值,还复制它的引用类型属性。深复制得到的新对象与原始对象的引用类型属性指向不同的对象

例如:在Java中, 如果需要对一个对象进行深复制, 需要对其中的引用类型的属性也进行复制,递归的方式来实现。

深复制能够保证复制出来的对象与原始对象的独立性,在修改新对象中的引用类型属性时不会影响到原始对象。

深复制通常用来复制可变类型的对象因为它能够保证对象的独立性。但是深复制的代价也比较高,需要编写大量的代码来实现,性能也比较低。

4.1、几种实现方法

深复制是指复制对象及其内部对象。

Java中可以使用以下几种方式实现深复制:

①序列化和反序列化:可以使用Java的序列化和反序列化机制,将对象写入流中,再从流中读取出来

②重写clone()方法:可以重写clone()方法,实现对象的深复制。

③使用Apache的Commons BeanUtils工具类:使用BeanUtils.cloneBean()方法实现对象的深复制。

5、内存空间

当使用原型模式克隆对象时,JVM虚拟机会在内存中为新对象分配一块新的内存空间。这块内存空间中会保存新对象的所有属性的值

如果使用浅复制,那么新对象中的引用类型属性指向的对象与原始对象中的引用类型属性指向的对象是同一个对象,在内存中只有一块空间,新对象中的引用类型属性与原始对象中的引用类型属性指向同一个对象。

如果使用深复制,那么新对象中的引用类型属性指向的对象与原始对象中的引用类型属性指向的对象是不同的对象,在内存中会有两块空间,新对象中的引用类型属性与原始对象中的引用类型属性指向不同的对象。

使用原型模式克隆对象时, 一般都需要重写Object的clone()方法, 使用重写的clone()方法可以更好的控制对象的克隆过程, 也可以在克隆过程中设置一些限制条件.

总结来说,使用原型模式克隆对象时,JVM虚拟机会在内存中为新对象分配一块新的内存空间,这块内存空间中会保存新对象的所有属性的值。对于浅复制和深复制的区别就在于,浅复制只复制对象的基本类型属性值,而深复制需要复制对象的所有属性值,并为新对象的引用类型属性分配新的内存空间。

6、Object clone()

Java的Object类中有一个clone()方法,它可以用来创建一个对象的副本。这个方法是一个受保护的方法默认实现是浅复制

clone()方法的原型如下:

这个方法有一个受保护的访问权限,所以如果要使用它,需要在类中重写这个方法。

如果一个类实现了Cloneable接口,那么这个类就可以使用Object类中的clone()方法。Cloneable接口是一个空接口,它只是告诉JVM这个类可以被克隆。

在重写clone()方法时,可调用super.clone()来实现浅复制,也可以自己实现对属性的复制来实现深复制。

如果在clone()方法中抛出CloneNotSupportedException异常,表示这个类不支持克隆操作,需要在类中重写clone()方法来解决。

实现clone()的代码示例如下:

上面的代码表示我们使用默认的浅复制.

如果要使用深复制, 就需要自己实现对属性的复制,如:

如果类中有一些不能被复制的属性, 那么在重写clone()方法的时候要考虑这些属性的复制方式.

总之, 使用Object类中的clone()方法可以创建一个对象的副本,但是默认实现只是浅复制, 若要进行深复制需要自己实现对属性的复制. 在使用clone()方法之前, 还要确保类实现了Cloneable接口.

7、代码实现

Java 代码实现可以如下:

1. class Student implements Cloneable {
2. private String name;
3. private int age;
4. private List<String> hobbies;
5. 
6. //深复制
7. public Student clone() throws CloneNotSupportedException {
8. Student student = (Student) super.clone();
9.         student.hobbies = new ArrayList<>(this.hobbies);
10. return student;
11.     }
12. //浅复制
13. public Student clone() throws CloneNotSupportedException {
14. Student student = (Student) super.clone();
15. return student;
16.    }
17. 
18. 
19. // setter and getter

代码中,深复制和浅复制的区别在于:有没有拷贝引用数据类型List<String>。

深复制中,重新new了一个ArrayList对象来复制原来的List对象,实现对象相互独立。

在Client中的使用如下:

1. Student student = new Student();
2. student.setName("John Doe");
3. student.setAge(20);
4. student.setHobbies(new ArrayList<>(Arrays.asList("reading", "running")));
5. // student.setHobbies(Arrays.asList("reading", "running"));
6. Student newStudent = student.clone();
7. // 修改原型对象
8. student.setAge(21);
9. //        System.out.println(student.getHobbies());
10. student.getHobbies().add("swimming");
11. 
12. // 实现深复制后的新对象不受影响
13. System.out.println(newStudent.getAge()); // 20
14. System.out.println(newStudent.getHobbies()); // [reading, running]

在上面的示例代码中,首先创建了一个student对象,并设置了其属性。然后使用clone()方法创建了一个新的对象newStudent。接着修改了student对象的属性,发现newStudent对象的属性并没有受到影响,证明实现了深拷贝。 需要注意的是,如果原型对象中存在引用其他对象,则需要对这些对象也进行复制。

8、UnsupportedOperationException

在上述的代码中有一个比较隐蔽的问题:UnsupportedOperationException(不支持的操作异常)

如上所示的两种方式:

①Arrays.asList("reading", "running")

②new ArrayList<>(Arrays.asList("reading", "running"))

若采用方式①,在使用add()或remove()方法时,会报UnsupportedOperationException异常!

采用方式②,把得到的对象的内容重新赋值给新的ArrayList对象,即可解决问题

8.1、Arrays.asList

Arrays.asList源码如下:

出现异常的原因,则是此处的ArrayList并非java.util.ArrayList,而是Arrays的一个内部类

(在Arrays类中并未找到相关的导包操作)

8.2、Arrays.ArrayList

Arrays.ArrayList源码如下:

Objects.requireNonNull():

由"①Arrays.asList("reading", "running")"知,源码中的array即为("reading", "running")。

则Arrays.ArrayList()中的a,即为array,a的类型为List。

综上,当client调用add()时,实际上调用的并不是List的add(),而是a.add(),而Arrays.ArrayList内部类中并没有编写add()方法!

8.3、AbstractList<E>

AbstractList<E>源码:

在AbstractList<E>中找到了add方法:

AbstractList中的add、set、remove方法都默认throw new UnsupportedOperationException()

9、使用场景

原型模式在下面几种情况下通常会被使用:

①在创建对象的过程中耗时较长或资源消耗较大。

特别是在对象的创建过程涉及复杂的构造函数或者创建过程涉及很多的资源消耗的时候,通过克隆 已有对象来代替重新创建对象可以显著降低系统资源消耗。

②创建新对象的类型已经确定,但是类的实例化过程不确定。

③创建新对象的类型已经确定,且需要大量的类似的对象。

另外需要注意线程安全问题,如果原型对象被多个线程同时使用,需要考虑同步机制。

10、优缺点

原型模式具有下面几个优点:

①通过克隆对象来创建新对象,避免了对象的重新创建过程,能够大幅度提高程序的性能。

②通过原型模式可以更加灵活地控制对象的创建过程

③在深复制时,能够对对象的引用类型属性进行复制,进而保证克隆出来的对象与原始对象的独立性

另外,原型模式也有一些缺点需要注意:

①需要为每一个类型编写一个克隆方法,这对全局所有类都是需要的,这将增加系统中类的个数。

②需要对每一个类都提供一个构造函数,这将增加类的复杂度。

③在进行深复制时,需要编写代码来复制引用类型的属性,这可能需要大量的时间和空间

11、总结

原型模式在选择时需要权衡其优缺点,并适当地使用。

它是一个比较简单的模式,但是需要注意的问题还是有一些的。

最后, 如果项目中需要大量的创建对象且对象的创建过程较为复杂,可以考虑使用原型模式来优化性能。

相关文章
|
17天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
17天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
设计模式 架构师 Java
设计模式之 5 大创建型模式,万字长文深剖 ,近 30 张图解!
设计模式是写出优秀程序的保障,是让面向对象保持结构良好的秘诀,与架构能力与阅读源码的能力息息相关,本文深剖设计模式之 5 大创建型模式。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
设计模式之 5 大创建型模式,万字长文深剖 ,近 30 张图解!
|
4月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑】设计模式——原型模式
对比原型模式和传统方式的实现思路、代码方案、优缺点,阐述原型模式的使用场景,以及深拷贝、浅拷贝等相关概念,并扩展原型模式在Spring源码中的应用。
|
4月前
|
设计模式 Java
Java设计模式-原型模式(3)
Java设计模式-原型模式(3)
Java设计模式-原型模式(3)
|
5月前
|
设计模式 存储 负载均衡
【五】设计模式~~~创建型模式~~~单例模式(Java)
文章详细介绍了单例模式(Singleton Pattern),这是一种确保一个类只有一个实例,并提供全局访问点的设计模式。文中通过Windows任务管理器的例子阐述了单例模式的动机,解释了如何通过私有构造函数、静态私有成员变量和公有静态方法实现单例模式。接着,通过负载均衡器的案例展示了单例模式的应用,并讨论了单例模式的优点、缺点以及适用场景。最后,文章还探讨了饿汉式和懒汉式单例的实现方式及其比较。
【五】设计模式~~~创建型模式~~~单例模式(Java)
|
5月前
|
设计模式 XML 存储
【三】设计模式~~~创建型模式~~~抽象工厂模式(Java)
文章详细介绍了抽象工厂模式,这是一种创建型设计模式,用于提供一个接口以创建一系列相关或相互依赖的对象,而不指定它们具体的类。通过代码示例和结构图,文章展示了抽象工厂模式的动机、定义、结构、优点、缺点以及适用场景,并探讨了如何通过配置文件和反射机制实现工厂的动态创建。
【三】设计模式~~~创建型模式~~~抽象工厂模式(Java)
|
5月前
|
设计模式 XML 存储
【二】设计模式~~~创建型模式~~~工厂方法模式(Java)
文章详细介绍了工厂方法模式(Factory Method Pattern),这是一种创建型设计模式,用于将对象的创建过程委托给多个工厂子类中的某一个,以实现对象创建的封装和扩展性。文章通过日志记录器的实例,展示了工厂方法模式的结构、角色、时序图、代码实现、优点、缺点以及适用环境,并探讨了如何通过配置文件和Java反射机制实现工厂的动态创建。
【二】设计模式~~~创建型模式~~~工厂方法模式(Java)
|
5月前
|
设计模式 XML Java
【一】设计模式~~~创建型模式~~~简单工厂模式(Java)
文章详细介绍了简单工厂模式(Simple Factory Pattern),这是一种创建型设计模式,用于根据输入参数的不同返回不同类的实例,而客户端不需要知道具体类名。文章通过图表类的实例,展示了简单工厂模式的结构、时序图、代码实现、优缺点以及适用环境,并提供了Java代码示例和扩展应用,如通过配置文件读取参数来实现对象的创建。
【一】设计模式~~~创建型模式~~~简单工厂模式(Java)
|
5月前
|
设计模式 存储 XML
[设计模式]创建型模式-抽象工厂模式
[设计模式]创建型模式-抽象工厂模式