- 原型模式比较简单,本质就是将一个设置好一部分公共属性的对象进行克隆,产生出大量的对象,再对每个对象进行相应的个性化处理
- 需要注意的是:对象克隆时,如果其成员变量中存在引用类型(数组、引用对象等),就必须解决浅拷贝问题
- 浅拷贝:对于被克隆对象的成员变量,如果是基本类型(包括String),则会直接克隆,但对于引用类型,则是引用被克隆对象的地址,也就是说克隆对象可以直接操作原对象的引用类型成员变量。
- 深拷贝:对象进行克隆时,将引用类型的成员变量也进行拷贝(该对象也需要实现Cloneable接口,否则不能拷贝),然后再赋值给克隆对象
原型模式(基本案例)
邮件类(个性属性多)
/**
* 邮件
*/
public class Mail implements Cloneable{
// 收件人
private String receiver;
// 邮件名称
private String subject;
// 称谓
private String appellation;
// 邮件内容
private String content;
// 邮件尾部
private String tail;
public Mail(AdvTemplate advTemplate){
this.content = advTemplate.getAdvContent();
this.subject = advTemplate.getAdvSubject();
}
@Override
public Mail clone(){
Mail mail = null;
try {
mail = (Mail) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return mail;
}
// Getter、Setter省略
}
模板类(公共属性多)
/**
* 广告信模板代码
*/
public class AdvTemplate {
// 广告信名称
private String advSubject = "XX银行国庆信用卡抽奖活动";
// 广告信内容
private String advContent = "国庆抽奖活动通知:只要刷卡就送100元话费充值券!";
public String getAdvSubject() {
return advSubject;
}
public String getAdvContent() {
return advContent;
}
}
入口类
public class PrototypeMain {
// 发送账单的数量,假定6
private static final int MAX_COUNT = 6;
public static void main(String[] args) {
// 模拟发送邮件
int i = 0;
Mail mail = new Mail(new AdvTemplate());
mail.setTail("XX银行版权所有");
while (i < MAX_COUNT) {
// 改动:通过克隆,而不是new的方式创建对象,该对象保留大部分细节,个性部分再修改即可
Mail cloneMail = mail.clone();
cloneMail.setAppellation(getRandString(5) + "先生(女士)");
cloneMail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");
// 然后发送邮件
sendMain(cloneMail);
i++;
}
}
private static void sendMain(Mail mail) {
System.out.println("标题:" + mail.getSubject() + "\t收件人:" + mail.getReceiver() + "\t...发送成功");
}
private static String getRandString(int maxLength) {
String source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuffer sb = new StringBuffer();
Random rand = new Random();
for (int i = 0; i < maxLength; i++) {
sb.append(source.charAt(rand.nextInt(source.length())));
}
return sb.toString();
}
}
结果
浅拷贝、深拷贝案例
/**
* 浅拷贝
* 1. 浅拷贝对对象中基础类型的成员变量(包括String)是直接拷贝的
* 2. 但是对于引用类型(数组、对象)的变量则不拷贝,而是采用地址引用,这样就会造成克隆对象可以操作原对象中的成员变量(对象类型)
* 3. 注意:引用类型的成员变量也必须实现Cloneable接口,否则就会编译报错
* 例如:List<String> list = new ArrayList<>()就会报错,ArrayList<String> list = new ArrayList<>() 才不会报错
*/
class ShallowCopyThing implements Cloneable{
private ArrayList<String> list = new ArrayList<>();
@Override
public ShallowCopyThing clone(){
ShallowCopyThing thing = null;
try {
// 这步只能算作浅拷贝,浅拷贝对于`引用类型`成员变量是地址引用,克隆的对象也能操作原对象的成员变量
thing = (ShallowCopyThing) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
// 设置List的值
public void setValue(String value){
this.list.add(value);
}
// 获取List的值
public ArrayList<String> getValue(){
return this.list;
}
}
/**
* 深拷贝
* 连同类中的成员变量一起拷贝
*/
class DeepCopyThing implements Cloneable{
private ArrayList<String> list = new ArrayList<>();
private Person per = new Person();
@Override
public DeepCopyThing clone(){
DeepCopyThing thing = null;
try {
// 这步只能算作浅拷贝,浅拷贝对于`引用类型`成员变量是地址引用,克隆的对象也能操作原对象的成员变量
thing = (DeepCopyThing) super.clone();
// 这步才算是深拷贝,对私有成员变量独立拷贝,再赋值给克隆对象
thing.list = (ArrayList<String>) this.list.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
// 设置List的值
public void setValue(String value){
this.list.add(value);
}
// 获取List的值
public ArrayList<String> getValue(){
return this.list;
}
}
public class ShallowAndDeepCopyMain {
public static void main(String[] args) {
shallowCopy();
deepCopy();
}
public static void deepCopy(){
DeepCopyThing thing = new DeepCopyThing();
thing.setValue("Good");
DeepCopyThing clone = thing.clone();
clone.setValue("Boy");
System.out.println("deepCopy: " + thing.getValue());
}
public static void shallowCopy(){
ShallowCopyThing thing = new ShallowCopyThing();
thing.setValue("Good");
ShallowCopyThing clone = thing.clone();
clone.setValue("Boy");
System.out.println("shallowCopy: " + thing.getValue()); // 结果是原对象的值会被拷贝的值操作,极不安全
}
}
结果
参考书籍
秦小波《设计模式之禅》