Java - GoF设计模式详解4(原型模式)
作者:hangge | 2023-03-03 08:59
四、原型模式
1,基本介绍
(1)原型模式(Prototype)。用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。用这种方式创建对象非常高效,根本无须知道要创建对象的确切类以及如何创建等细节。
(2)该模式中包含的角色及其职责如下:
- 抽象原型类(Prototype):规定了具体原型对象必须实现的的 clone() 方法。
- 具体原型类(ConcretePrototype):实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类(Client):使用具体原型类中的 clone() 方法来复制新的对象。

(3)原型模式的克隆分为浅克隆和深克隆:
- 浅克隆(浅复制):创建一个新对象,新对象的属性和原来对象完全相同,对于非基本数据类型属性(即引用数据类型属性),仍指向原有属性所指向的对象的内存地址。
- 深克隆(深复制):创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
2,浅克隆样例
(1)Java 中的 Object 类提供了浅克隆的 clone() ⽅法(访问权限为 protected),具体原型类只要实现 Cloneable 接⼝就可实现对象的浅克隆,这⾥的 Cloneable 接⼝就是抽象原型类。
public class Computer implements Cloneable {
private String name;
private int price;
public Computer() {
}
public Computer(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String toString() {
return "电脑型号(" + this.name + ")价格(" + this.price + ")";
}
@Override
protected Computer clone(){
try {
return(Computer)super.clone();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
(2)测试一下,可以看到浅克隆会复制原对象的所有基本数据类型的成员变量值,原对象里面基本数据类型值修改后也不影响浅克隆出来的对象:
public class Test {
public static void main(String[] args) {
Computer computer1 = new Computer("联想A100", 5300);
Computer computer2 = computer1.clone();
System.out.println("原对象:" + computer1.toString());
System.out.println("克隆对象:" + computer2.toString());
System.out.println("--- 修改原对象 ---");
computer1.setName("华硕k909");
computer1.setPrice(8888);
System.out.println("原对象:" + computer1.toString());
System.out.println("克隆对象:" + computer2.toString());
}
}
(3)但对于引用数据类型的成员变量,浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。我们对上面的电脑类 Computer 稍作改动,增加一个硬件配置的成员变量 Hardware:
//电脑类
public class Computer implements Cloneable {
private String name;
private int price;
private Hardware hardware;
public Computer() {
}
public Computer(String name, int price, Hardware hardware) {
this.name = name;
this.price = price;
this.hardware = hardware;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Hardware getHardware() {
return hardware;
}
public void setHardware(Hardware hardware) {
this.hardware = hardware;
}
public String toString() {
return "电脑型号(" + this.name + ")价格(" + this.price + ")" + this.hardware.toString();
}
@Override
protected Computer clone(){
try {
return(Computer)super.clone();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
//硬件配置类
public class Hardware {
private String cpu;
private String disk;
public Hardware() {
}
public Hardware(String cpu, String disk) {
this.cpu = cpu;
this.disk = disk;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getDisk() {
return disk;
}
public void setDisk(String disk) {
this.disk = disk;
}
public String toString() {
return "CPU(" + this.cpu + ")硬盘(" + this.disk + ")";
}
}
(4)再次测试下,由于两个对象的该成员变量都指向同一个实例,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值:
public class Test {
public static void main(String[] args) {
Hardware hardware = new Hardware("i5", "500GB");
Computer computer1 = new Computer("联想A100", 5300, hardware);
Computer computer2 = computer1.clone();
System.out.println("原对象:" + computer1.toString());
System.out.println("克隆对象:" + computer2.toString());
System.out.println("--- 修改原对象 ---");
computer1.setName("华硕k909");
computer1.setPrice(8888);
computer1.getHardware().setCpu("i7");
computer1.getHardware().setDisk("1TB");
System.out.println("原对象:" + computer1.toString());
System.out.println("克隆对象:" + computer2.toString());
}
}
3,深克隆样例
(1)要实现深克隆有两种方式,一种就是对于成员对象也需要实现 Cloneable 接口,覆盖 clone 方法。复制对象的 clone 方法中需要对成员变量重新赋值为克隆出来的新成员对象,每一层对象都进行了复制。
//电脑类
public class Computer implements Cloneable {
private String name;
private int price;
private Hardware hardware;
public Computer() {
}
public Computer(String name, int price, Hardware hardware) {
this.name = name;
this.price = price;
this.hardware = hardware;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Hardware getHardware() {
return hardware;
}
public void setHardware(Hardware hardware) {
this.hardware = hardware;
}
public String toString() {
return "电脑型号(" + this.name + ")价格(" + this.price + ")" + this.hardware.toString();
}
@Override
protected Computer clone(){
try {
Computer computer = (Computer)super.clone();
computer.setHardware(this.hardware.clone());
return computer;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
//硬件配置类
public class Hardware implements Cloneable {
private String cpu;
private String disk;
public Hardware() {
}
public Hardware(String cpu, String disk) {
this.cpu = cpu;
this.disk = disk;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getDisk() {
return disk;
}
public void setDisk(String disk) {
this.disk = disk;
}
public String toString() {
return "CPU(" + this.cpu + ")硬盘(" + this.disk + ")";
}
@Override
protected Hardware clone(){
try {
return(Hardware)super.clone();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
(2)测试一下,由于两个对象的该成员变量指向不同实例,在一个对象中修改该成员变量不会影响到另一个对象的该成员变量值:
public class Test {
public static void main(String[] args) {
Hardware hardware = new Hardware("i5", "500GB");
Computer computer1 = new Computer("联想A100", 5300, hardware);
Computer computer2 = computer1.clone();
System.out.println("原对象:" + computer1.toString());
System.out.println("克隆对象:" + computer2.toString());
System.out.println("--- 修改原对象 ---");
computer1.setName("华硕k909");
computer1.setPrice(8888);
computer1.getHardware().setCpu("i7");
computer1.getHardware().setDisk("1TB");
System.out.println("原对象:" + computer1.toString());
System.out.println("克隆对象:" + computer2.toString());
}
}
(3)上面这种对所有成员对象实现 Cloneable 接口,覆盖 clone 方法,并且重新赋值的方式实现深克隆还是略显麻烦。我们还可以使用序列化的方式实现深克隆。即以字节流的方式,把对象数据保存在流当中,再从流中取出来,取出来的时候就会创建一个新的对象,而不用再引用同一个数据对象了。
注意:要复制的对象(包括成员对象)要实现序列化接口 Serializable
//电脑类
public class Computer implements Cloneable, Serializable {
private String name;
private int price;
private Hardware hardware;
public Computer() {
}
public Computer(String name, int price, Hardware hardware) {
this.name = name;
this.price = price;
this.hardware = hardware;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Hardware getHardware() {
return hardware;
}
public void setHardware(Hardware hardware) {
this.hardware = hardware;
}
public String toString() {
return "电脑型号(" + this.name + ")价格(" + this.price + ")" + this.hardware.toString();
}
@Override
protected Computer clone(){
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
Computer copyObj = (Computer)ois.readObject();
return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
}
//硬件配置类
public class Hardware implements Serializable {
private String cpu;
private String disk;
public Hardware() {
}
public Hardware(String cpu, String disk) {
this.cpu = cpu;
this.disk = disk;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getDisk() {
return disk;
}
public void setDisk(String disk) {
this.disk = disk;
}
public String toString() {
return "CPU(" + this.cpu + ")硬盘(" + this.disk + ")";
}
}
附:Spring 中的原型模式
(1)在 Spring 中,原型模式主要应用在 Bean 的作用域上。Spring 中提供了两种作用域:单例和原型。当一个 Bean 被配置为原型作用域时,使用原型模式来创建对象。这就类似于使用原型模式来创建对象。
(2)举例来说,假设我们有一个 Person 类,我们可以在配置文件中将其配置为原型作用域,这样每次请求都会创建一个新的 Person 对象:
<bean id="person" class="com.example.Person" scope="prototype"></bean>
- 或者在注解中配置,效果是一样的:
@Scope("prototype")
@Component
public class Person {
// ...
}
全部评论(0)