原型模式(Prototype Pattern)是一种创建型设计模式,它通过拷贝现有的实例来创建新的实例,而不是通过新建实例。这种方式可以避免复杂的构造过程,同时还能保持对象的创建和使用分离,提高系统的灵活性和扩展性。
了解原型模式之前需要先搞懂浅拷贝和深拷贝。
浅拷贝指的是创建一个新对象,然后将原对象的非静态字段复制到新对象中。如果字段是基本数据类型,那么直接复制其值;如果字段是对象引用,则复制引用本身,而不是引用的对象。这意味着新对象和原对象的引用字段指向同一个对象。
深拷贝意味着不仅要复制对象本身,还要复制对象中所有引用的对象,以及这些引用对象中引用的其他对象,直到所有相关对象都被复制。
另外java中有一些特殊的引用类型(不可变对象),如下。这些引用类型创建的对象是不可变对象,在拷贝是都是理解成是一种深拷贝。
在Java中,不可变对象是指一旦创建后,其状态(即对象的字段值)就不能被改变的对象。不可变对象具有线程安全性、可缓存性和可共享性等优点。以下是一些常见的不可变引用类型对象:基本数据类型的包装类
Integer
Byte
Short
Long
Float
Double
Character
Boolean
这些包装类都是基本数据类型的对应引用类型。它们是不可变的,因为它们的值一旦被设置,就不能被改变。例如,当你对一个Integer对象进行加法运算时,实际上会创建一个新的Integer对象来表示新的值,而不是修改原来的对象。字符串类
String
String类是Java中最常用的不可变类之一。字符串对象一旦被创建,其内容就不能被改变。例如,当你使用+操作符连接两个字符串时,会创建一个新的字符串对象,而不是修改原来的字符串对象。原子类
AtomicInteger
AtomicLong
AtomicBoolean
其他java.util.concurrent.atomic包中的原子类
这些原子类提供了对基本数据类型的安全操作,它们是不可变的,因为它们的值只能通过原子操作来更新,而不是直接修改对象的状态。其他不可变类
BigDecimal 和 BigInteger
这些类用于表示任意精度的数值。它们是不可变的,因为每次进行数学运算时,都会返回一个新的对象来表示结果,而不是修改原来的对象。java.time包中的日期和时间类
例如:LocalDate
LocalTime
LocalDateTime
ZonedDateTime
Duration
Period
这些类是Java 8引入的新的日期和时间API的一部分。它们都是不可变的,每次进行日期或时间的修改操作时,都会返回一个新的对象。不可变对象的设计原则
要设计一个不可变对象,通常需要遵循以下原则:所有字段都是final的:确保字段一旦被初始化,就不能被改变.
不提供修改字段值的方法:不提供任何可以修改对象状态的方法.
确保对象的所有字段都是不可变的:如果对象的字段是引用类型,确保这些引用类型也是不可变的.
如果需要提供访问字段的方法,返回字段的副本而不是引用:例如,对于包含可变对象的字段,返回字段的副本,以防止外部代码修改对象的状态.
通过设计不可变对象,可以提高程序的安全性和可维护性,特别是在多线程环境中.
原型模式核心概念
- 原型(Prototype):声明一个克隆自身的接口,用于创建当前对象的副本。在Java中,通常通过实现
Cloneable
接口并重写clone()
方法来实现原型模式。 - 具体原型(ConcretePrototype):实现原型接口,提供具体的克隆实现,生成自己的副本。
实现步骤
- 实现
Cloneable
接口:让类实现Cloneable
接口,表示该类的对象可以被克隆。 - 重写
clone()
方法:在类中重写Object
类的clone()
方法,以实现深拷贝或浅拷贝。 - 创建原型对象:创建一个原型对象,作为后续克隆的模板。
- 克隆原型对象:通过调用原型对象的
clone()
方法,创建新的对象实例.
示例代码
以下是一个简单的Java示例,展示如何使用原型模式:
import java.util.Date;// 具体原型类
class Resume implements Cloneable {private String name;private String sex;private String age;private Date birthDate; // 假设包含日期对象public Resume(String name, String sex, String age) {this.name = name;this.sex = sex;this.age = age;this.birthDate = new Date(); // 初始化日期对象}@Overrideprotected Object clone() throws CloneNotSupportedException {// 实现深拷贝//super.clone()是调用Object类的clone()方法,它会创建一个新的对象,并将原对象的字段值复制到新对象中。对于基本数据类型的字段,值会被直接复制;而对于引用类型的字段,复制的是引用本身,而不是引用所指向的对象. 这一步本身是浅拷贝.Resume clonedResume = (Resume) super.clone();clonedResume.birthDate = (Date) this.birthDate.clone();return clonedResume;}@Overridepublic String toString() {return "Resume{" +"name='" + name + '\'' +", sex='" + sex + '\'' +", age='" + age + '\'' +", birthDate=" + birthDate +'}';}
}public class PrototypePatternDemo {public static void main(String[] args) throws CloneNotSupportedException {Resume originalResume = new Resume("John Doe", "Male", "30");System.out.println("Original Resume: " + originalResume);Resume clonedResume = (Resume) originalResume.clone();System.out.println("Cloned Resume: " + clonedResume);}
}
优缺点
- 优点:
- 性能优势:通过拷贝现有对象,避免了复杂的构造过程,特别是在对象创建过程中需要进行大量数据计算或资源获取时.
- 代码简洁:简化了对象的创建代码,使得代码更加简洁和易于维护.
- 缺点:
- 实现复杂:需要实现深拷贝,对于包含复杂对象引用的类,实现起来可能比较复杂.
- 对类的约束:需要实现
Cloneable
接口,并且所有涉及的对象都需要支持拷贝操作,这可能对类的设计有一定的约束.
适用场景
- 当一个系统应该独立于其产品的创建、组合和表示时.
- 当需要通过动态指定创建对象的类别、数目和初始化参数时.
- 当对象的创建过程复杂,且需要避免重复创建大量相似对象时.