原型模式

原型模式

原型模式⼀种创建型设计模式,该模式的核⼼思想是基于现有的对象创建新的对象,而不是从头开始创建。
在原型模式中,通常有⼀个原型对象,它被⽤作创建新对象的模板。新对象通过复制原型对象的属性和状态来创
建,而无需知道具体的创建细节。

为什么要使用原型模式

相比于直接实例化对象,通过原型模式复制对象可以减少资源消耗,提⾼性能,尤其在对象的创建过程复杂或对象
的创建代价较⼤的情况下。当需要频繁创建相似对象、并且可以通过克隆避免重复初始化工作的场景时可以考虑使
⽤原型模式,在克隆对象的时候还可以动态地添加或删除原型对象的属性,创造出相似但不完全相同的对象,提高
了灵活性。
但是使用原型模式也需要考虑到如果对象的内部状态包含了引⽤类型的成员变量,那么实现深拷⻉就会变得较为复
杂,需要考虑引⽤类型对象的克隆问题。

原型模式在现有的很多语言中都有应用,⽐如以下几个经典例子。
Java 提供了 Object 类的 clone() 方法,可以实现对象的浅拷贝。类需要实现 Cloneable 接口并重写
clone() 方法。
在 .NET 中, ICloneable 接⼝提供了 Clone 方法,可以⽤于实现对象的克隆。
Spring 框架中的 Bean 的作⽤域之⼀是原型作用域(Prototype Scope),在这个作⽤域下,Spring 框架会
为每次请求创建⼀个新的 Bean 实例,类似于原型模式。

image-20240105113603895

抽象原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class Prototype implements Cloneable {

public abstract Prototype clone();
public abstract String getDetails();

// 公共的 clone ⽅法
public Prototype clonePrototype() {
try {
return (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}

}

具体原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class RectanglePrototype extends Prototype {
private String color;
private int width;
private int height;
// 构造⽅法
public RectanglePrototype(String color, int width, int height) {
this.color = color;
this.width = width;
this.height = height;

}


// 克隆⽅法
@Override
public Prototype clone() {
return clonePrototype();
}

// 获取矩形的详细信息
@Override
public String getDetails() {
return "Color: " + color + ", Width: " + width + ", Height: " + height;
}

}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client {

public static void main(String[] args) {

RectanglePrototype prototype = new RectanglePrototype("red", 10, 5);
Prototype prototype1 = prototype.clone();
Prototype prototype2 = prototype.clone();
prototype2.setColor("blue");

System.out.println(prototype1.getDetails());
System.out.println(prototype2.getDetails());
}

}

Color: red, Width: 10, Height: 5
Color: blue, Width: 10, Height: 5

这个例子使用的是浅拷贝,由于原型对象中没有引用类型,所以不影响拷贝对象的修改

如果原型对象中有引用类型,建议使用深拷贝

浅拷贝和深拷贝

浅拷贝:当拷贝对象包含基本数据类型(如int、long)或者不可变的对象(如字符串、基本类型的包装类)时,会直接将这些属性复制到新的对象中。而原型对象中的引用对象会把内存中的地址复制给克隆对象。此时,两个引用对象共享了一个私有变量,你改我改大家都能改。

深拷贝:不管原型对象属性是简单数据类型还是引用对象类型都会完全的复制一份到新的对象中。两个对象之间互不影响

image-20240105132903500 image-20240105132917203

深拷贝克隆

需要对引用对象再做一次克隆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class User implements Cloneable {
// 用户姓名
public String userName ;
// 用户年龄
public int age ;
// 用户地址
public Address userAddress ;

@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
", userAddress=" + userAddress +
'}';
}



/**
* 复写克隆方法 clone()
*/
@Override
protected User clone() throws CloneNotSupportedException {
// 克隆User对象
User user = (User) super.clone() ;
// 克隆Address对象,也就是说把类中的类对象也进行一次拷贝,达到深拷贝
user.userAddress = userAddress.clone() ;
return user;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Address implements Cloneable{
public Address(String addressName, String city) {
this.addressName = addressName;
this.city = city;
}

public String addressName ;
public String city ;

@Override
public String toString() {
return "Address{" +
"addressName='" + addressName + '\'' +
", city='" + city + '\'' +
'}';
}

@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}public class Address implements Cloneable{
public Address(String addressName, String city) {
this.addressName = addressName;
this.city = city;
}

public String addressName ;
public String city ;

@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserClient {
public static void main(String[] args){
User user = new User() ;
user.userName = "Novate" ;
user.age = 18 ;
user.userAddress = new Address("陕西西安" , "西安") ;

// 深拷贝
try {
// 对User进行拷贝,copyUser就是拷贝的对象
User copyUser = user.clone() ;
// 把拷贝的姓名和地址做下修改
copyUser.userAddress.addressName = "北京" ;
copyUser.userAddress.city = "中国北京";
copyUser.userName = "王子文" ;
// 姓名:Novate, 地址:北京
System.out.println(user.toString());
// 姓名:王子文, 地址:北京
System.out.println(copyUser.toString());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

User{userName=’Novate’, age=18, userAddress=Address{addressName=’陕西西安’, city=’西安’}}
User{userName=’王子文’, age=18, userAddress=Address{addressName=’北京’, city=’中国北京’}}

可以看到Address成功修改了,完成了深拷贝

原型模式的优点

  • 原型模式是在内存中进行二进制流的拷贝,要比直接new一个对象性能好,特别是在一个循环体内创建大量对象时。
  • 原型模式可以简化对象创建的过程,可以直接拷贝现有的原型实例的值,实现对象复用。

适用场景

1、性能优化场景

类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。

2、安全访问场景

当某个对象对外可能是只读的,为了防止外部对这个只读对象的修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。

3、一个对象多个修改者的场景

一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。


原型模式
http://example.com/2023/10/15/计算机基础/设计模式/04-原型模式/
作者
PALE13
发布于
2023年10月15日
许可协议