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

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

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、总结

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

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

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

相关文章
|
9月前
|
设计模式 Java 数据库连接
【设计模式】【创建型模式】工厂方法模式(Factory Methods)
一、入门 什么是工厂方法模式? 工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个用于创建对象的接口,但由子类决定实例化哪个类。工厂方法模式使类的实例化延迟
285 16
|
9月前
|
设计模式 缓存 安全
【设计模式】【创建型模式】单例模式(Singleton)
一、入门 什么是单例模式? 单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。它常用于需要全局唯一对象的场景,如配置管理、连接池等。 为什么要单例模式? 节省资源 场景:某些对象创
347 15
|
9月前
|
设计模式 JavaScript Java
【设计模式】【创建型模式】原型模式(Prototype)
一、入门 什么是原型模式? 原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是通过实例化类。 原型模式的核心是克隆(Clone),即通过复制现有
264 15
|
9月前
|
设计模式 Java Apache
【设计模式】【创建型模式】建造者模式(Builder)
一、入门 什么是建造者模式? 建造者模式(Builder Pattern)是一种创建型设计模式,用于逐步构建复杂对象。 它通过将对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。 为什么
488 14
|
9月前
|
设计模式 Java 关系型数据库
【设计模式】【创建型模式】抽象工厂模式(Abstract Factory)
一、入门 什么是抽象工厂模式? 抽象工厂模式是一种创建型设计模式,它提供了一个接口,用于创建相关或依赖对象的家族,而不需要指定具体的类。 简单来说,抽象工厂模式是工厂方法模式的升级版,它能够创建一组相
325 14
|
11月前
|
设计模式 XML Java
设计模式觉醒系列(03)创建型模式的5个设计模式 | 一口气讲全讲透
本文详细介绍了设计模式中的创建型模式,包括建造者模式、原型模式、单例模式、工厂方法模式和抽象工厂模式。创建型模式关注对象的创建过程,隐藏了创建细节,以提高代码的可维护性和可扩展性。通过具体的实战demo和应用场景分析,展示了每种模式的特点和优势。例如,建造者模式适用于复杂对象的分步骤构建;原型模式通过复制对象实现高效复用;单例模式确保全局唯一实例;工厂方法模式和抽象工厂模式则提供了灵活的对象创建机制,支持多类型产品族的生产。这些模式在实际开发中能够简化客户端代码,提升系统灵活性和复用性。
|
设计模式 存储 Java
「全网最细 + 实战源码案例」设计模式——原型模式
原型模式(Prototype Pattern)是一种创建型设计模式,通过复制现有对象来创建新对象,适用于创建成本高或复杂的对象场景。其核心思想是“克隆”,避免直接实例化类。结构上分为抽象原型类、具体原型类和客户端。优点包括减少对象创建成本、隐藏复杂性、简化实例创建;缺点是处理循环引用的复杂对象时较为麻烦。实现步骤为定义原型类、重写`clone()`方法并调用。注意事项包括浅拷贝与深拷贝的区别及`Cloneable`接口的使用。
229 20
|
设计模式 架构师 Java
设计模式之 5 大创建型模式,万字长文深剖 ,近 30 张图解!
设计模式是写出优秀程序的保障,是让面向对象保持结构良好的秘诀,与架构能力与阅读源码的能力息息相关,本文深剖设计模式之 5 大创建型模式。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
设计模式之 5 大创建型模式,万字长文深剖 ,近 30 张图解!
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
1070 2
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析