码猿技术专栏

微信公众号:码猿技术专栏

设计模式之原型模式

定义

  • 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要 在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

  • 原型模式可以分为浅克隆和深度克隆

角色

角色

java语言中实现克隆的两种方式

  1. 直接创建一个对象,然后设置成员变量的值
    1
    2
    3
    Obj obj=new Obj(); //创建一个新的对象
    obj.setName(this.name); //设置其中变量的值
    obj.setAge(this.age);
  1. 实现cloneable接口

浅克隆

  • 如果克隆的对象的成员变量是值类型的,比如int,double那么使用浅克隆就可以实现克隆完整的原型对象,但是如果其中的成员变量有引用类型的,那么这个引用类型的克隆过去的其实是地址,克隆对象的这个引用类型变量改变了,那么原来变量的值也是会改变的。

  • 简单的说,浅克隆只能复制值类型的,对于引用类型的数据只能复制地址

浅克隆

实例

  • 一个公司出版周报,那么这个周报的格式一般是相同的,只是将其中的内容稍作修改即可。但是一开始没有这个原型,员工每周都需要重新手写这个周报,现在有了这个周报的原型,只需要在这个clone这个原型,然后在其基础上修改即可。

周报

  • 其中的Cloneable就是抽象原型类
  • 附件类(这个是一个引用类型的对象,验证浅克隆只是复制其中的地址,如果两个对象中的任何一个改变了这个变量的值,那么另外一个也会随之改变)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* 附件类,这个是周报的附件
*/
public class Attachment {
private String name; // 名称

public Attachment(String name) {
super();
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

  • 周报的类(其中实现了Cloneable接口)
    • 其中的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
25
26
27
28
29
30
31
32
33
34
35
36
/*
* 这个是周报类,这个类是实现接口Prototype这个接口的
*/
public class WeeklyLog implements Cloneable {
private String name; // 姓名
private String date; // 日期
private String content; // 内容
private Attachment attachment; //附件,是一个引用对象,这个只能实现浅克隆
public WeeklyLog() {
super();
}
/**
* 构造方法
*/
public WeeklyLog(String name, String date, String content) {
super();
this.name = name;
this.date = date;
this.content = content;
}
/**
* 提供一个clone方法,返回的是一个clone对象
*/
public WeeklyLog clone() {
Object object = null; // 创建一个Object对象
try {
object = super.clone(); // 直接调用clone方法,复制对象
return (WeeklyLog) object; // 返回即可
} catch (CloneNotSupportedException e) {
System.out.println("这个对象不能复制.....");
return null;
}
}

}

  • 测试类
    • 测试浅克隆的值类型是是否完成复制了
    • 测试引用类型的值能否完成克隆,还是只是复制了一个引用地址
    • 从结果来看,对象是完成复制了,因为判断两个对象的地址是不一样的,但是其中的引用类型的成员变量没有完成复制,只是复制了一个地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {

WeeklyLog p1 = new WeeklyLog("陈加兵", "第一周", "获得劳动模范的称号..."); // 创建一个对象
Attachment attachment = new Attachment("消息");
p1.setAttachment(attachment); // 添加附件
WeeklyLog p2 = p1.clone();
System.out.println(p1 == p2); // 判断是否正确
p2.setName("Jack"); // 修改P2对象的内容
p2.setDate("第二周");
p2.setContent("工作认真.....");
System.out.println(p2.getName());
// 返回true,可以知道这两个附件的地址是一样的
System.out.println(p1.getAttachment() == p2.getAttachment());
}
}

总结

  • 浅克隆对于值类型的数据可以复制成功,但是对于引用卡类型的数据只能复制一个地址,如果一个对象中的引用类型的变量的值改变了,那么另外一个也会随之改变

深度克隆

  • 浅克隆只能完成复制值类型,深度克隆可以完成复制引用类型和值类型

深度克隆

条件

  1. 引用类型的变量类实现序列化(实现Serializabl接口)
  2. 需要克隆的类实现序列化(实现Serializable接口)

为什么实现序列化

  • 因为深度克隆的实现的原理是使用输入和输出流,如果想要将一个对象使用输入和输出流克隆,必须序列化。

实现

  • 附件类(引用类型的成员变量,实现序列化)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /*
    * 附件类,这个是周报的附件
    */
    public class Attachment implements Serializable{
    private static final long serialVersionUID = -799959163401886355L;
    private String name; // 名称
    public Attachment(String name) {
    super();
    this.name = name;
    }
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    }
  • 周报类(需要克隆的类,因为其中有引用类型的成员变量,因此需要实现序列化)

    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
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    /*
    * 这个是周报类,这个类是实现接口Prototype这个接口的
    */
    public class WeeklyLog implements Serializable {
    private static final long serialVersionUID = -8782492113927035907L;
    private String name; // 姓名
    private String date; // 日期
    private String content; // 内容
    private Attachment attachment; // 附件,是一个引用对象,这个只能实现浅克隆
    public WeeklyLog() {
    super();
    }
    /**
    * 构造方法
    */
    public WeeklyLog(String name, String date, String content) {
    super();
    this.name = name;
    this.date = date;
    this.content = content;
    }
    /**
    * 提供一个clone方法,返回的是一个clone对象
    */
    public WeeklyLog clone() {
    // 将对象写入到对象流中
    ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
    try {
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(
    arrayOutputStream); // 创建对象输出流
    objectOutputStream.writeObject(this); // 将这个类的对象写入到输出流中
    } catch (IOException e) {
    e.printStackTrace();
    return null;
    }

    // 将对象从流中读出
    ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(
    arrayOutputStream.toByteArray());
    WeeklyLog weeklyLog;
    try {
    ObjectInputStream objectInputStream = new ObjectInputStream(
    arrayInputStream);// 新建对象输入流
    weeklyLog = (WeeklyLog) objectInputStream.readObject(); // 读取对象从流中
    return weeklyLog;
    } catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
    return null;
    }

    }

    }
  • 测试类

    • 从中可以看出其中的附件地址是不同的,如果一个对象的附件变量改变了,那么另外一个将保持不变,因此实现了深度克隆,是两个完全不同的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {

WeeklyLog p1 = new WeeklyLog("陈加兵", "第一周", "获得劳动模范的称号..."); // 创建一个对象
Attachment attachment = new Attachment("消息");
p1.setAttachment(attachment); // 添加附件
WeeklyLog p2 = p1.clone();
System.out.println(p1 == p2); // 判断是否正确
p2.setName("Jack"); // 修改P2对象的内容
p2.setDate("第二周");
p2.setContent("工作认真.....");
System.out.println(p2.getName());
//返回false,可以看出这个是不同的地址,因此完成了深克隆
System.out.println(p1.getAttachment() == p2.getAttachment());
}
}

总结

  • 因为深度克隆使用的是将对象写入输入和输出流中的,因此需要实现序列化,否则将不能完成

总结

  1. 浅克隆只能克隆对象中的值类型,不能克隆有引用类型成员变量的对象

  2. 使用深度克隆:

    • 引用类型的成员变量的类必须实现序列化
    • 需要克隆的类必须实现序列化

笔者有话说

  • 最近建了一个微信交流群,提供给大家一个交流的平台,扫描下方笔者的微信二维码,备注【交流】,我会把大家拉进群

欢迎关注我的其它发布渠道