面向对象的编程中,创建一个对象是要开销资源的,比如java中通过new关键字来创建对象。但是有的时候,我们要创建大量的对象而这些对象的大部分属性都是相同只有部分属性会有变化。此时还使用new来创建对象,其开销的资源就比较大了,而且效率不高。于是我们就可以用到原型模式。

原型模式的定义是:通过编程语言(比如Java或C++)等提供的对象克隆(复制)方法来创建对象,而不是通过new关键字来创建对象。这种模式需要先有一个相当于对象模板的类,然后通过克隆的方式快速生成和原型具有相同或者相似属性的新对象。

原型模式的UML类图如下:

image.png

在这个模式中,其核心结构:

  • 首先定义一个克隆方法的接口,所有具体的原型都实现这个接口
  • 然后创建一个具体的原型类,实现这个接口并提供一个clone方法,让它能够复制自身
  • 最后,在调用的时候通过原型对象的clone方法来创建新对象。

举一个具体的例子,比如我们的系统中要提供一个邮件订阅的功能,当我们向订阅者发送邮件的时候,邮件内容基本是相同的,每一封邮件的区别可能只是收件人地址或者称呼有区别,那么我们就可以通过原型模式来快速创建邮件对象。
下面是实例代码,在这个例子中我们假设给两类用户发邮件,一类用来通知订单发货、一类用来通知用户的积分情况。

首先创建一个邮件对象接口

1
2
3
4
5
6
7
8
9
package protyotype;
// 邮件原型接口(抽象原型)
public interface Email extends Cloneable {
Email clone();
void setRecipient(String recipient);
void setContent(String content);
void send();
}

然后在具体的通知邮件对象中实现克隆方法

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
package protyotype;

public class NotificationEmail implements Email {
private String subject = "[系统通知] "; // 固定主题前缀
private String recipient; // 收件人(克隆后修改)
private String content; // 内容(克隆后修改)
private String footer = "\n\n---\n系统自动发送,请勿回复"; // 固定签名

@Override
public Email clone() {
try {
return (NotificationEmail) super.clone(); // 浅克隆
} catch (CloneNotSupportedException e) {
throw new RuntimeException("克隆失败", e);
}
}

@Override
public void setRecipient(String recipient) {
this.recipient = recipient;
}

@Override
public void setContent(String content) {
this.content = content;
}

@Override
public void send() {
System.out.println("发送邮件至:" + recipient);
System.out.println("主题:" + subject);
System.out.println("内容:" + content + footer);
System.out.println("-------------------");
}
}

最后在客户端中调用,使用clone方法来创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package protyotype;
// 客户端:批量发送邮件

public class EmailClient {
public static void main(String[] args) {
// 创建原型模板(只需初始化一次)
Email template = new NotificationEmail();

// 克隆模板生成新邮件,并修改差异部分
Email email1 = template.clone();
email1.setRecipient("user1@example.com");
email1.setContent("您的订单已发货,物流单号:12345");
email1.send();

Email email2 = template.clone();
email2.setRecipient("user2@example.com");
email2.setContent("您的会员积分已到账,当前积分:500");
email2.send();
}
}

总之,原型模式在应对批量创建相似属性对象时具有优势,而且灵活性更高,其使用编程语言内置的克隆方法也更加节省资源开销,在一些诸如邮件通知、消息推送或者报表生成等场景中较为常用。