Java設計模式之原型模式
//首先新建一個類Mail
public class Mail {
//這個類總共有三個屬性
private String name;
private String emailAddress;
private String content;
public Mail(){
System.out.println("Mail class constructor");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Mail{" +
"name='" + name + '\'' +
", emailAddress='" + emailAddress + '\'' +
", content='" + content + '\'' +
'}';
}
}
//新建另外一個類
public class MailUtil {
public static void sendMail(Mail mail){
String content="{0}同學,郵件地址:{1},內容:{2}";
System.out.println(MessageFormat.format(content,mail.getName()
,mail.getEmailAddress(),mail.getContent()));
}
public static void saveOriginalContent(Mail mail){
System.out.println("保存原始內容:"+mail.getContent());
}
}
//調用
public static void main(String[]a){
Mail mail=new Mail();
mail.setContent("初始化模板");
for (int i=0;i<10;i++){
mail.setName("姓名"+i);
mail.setEmailAddress("郵箱"+i);
mail.setContent("郵件內容。。。");
MailUtil.sendMail(mail);
}
MailUtil.saveOriginalContent(mail);
}
//結果
Mail class constructor
姓名0同學,郵件地址:郵箱0,內容:郵件內容。。。
姓名1同學,郵件地址:郵箱1,內容:郵件內容。。。
姓名2同學,郵件地址:郵箱2,內容:郵件內容。。。
姓名3同學,郵件地址:郵箱3,內容:郵件內容。。。
姓名4同學,郵件地址:郵箱4,內容:郵件內容。。。
姓名5同學,郵件地址:郵箱5,內容:郵件內容。。。
姓名6同學,郵件地址:郵箱6,內容:郵件內容。。。
姓名7同學,郵件地址:郵箱7,內容:郵件內容。。。
姓名8同學,郵件地址:郵箱8,內容:郵件內容。。。
姓名9同學,郵件地址:郵箱9,內容:郵件內容。。。
保存原始內容:郵件內容。。。
這裏有兩個問題,第一是MailUtil.saveOriginalContent(mail)方法應該是保存原始內容“初始化模板”而不是for循環裏面最後被賦值了。第二個問題是考慮實例化Mail對象比較消耗資源的問題。
下面看看引入原型模式後的優雅實現方法。
//Mail類實現Cloneable接口
public class Mail implements Cloneable{
private String name;
private String emailAddress;
private String content;
public Mail(){
System.out.println("Mail class constructor");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Mail{" +
"name='" + name + '\'' +
", emailAddress='" + emailAddress + '\'' +
", content='" + content + '\'' +
'}'+super.toString();
}
//重寫此方法
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("clone 方法");
return super.clone();
}
}
public class MailUtil {
public static void sendMail(Mail mail){
}
public static void saveOriginalContent(Mail mail){
System.out.println("保存原始內容:"+mail.getContent());
}
}
//調用
public static void main(String[]a) throws CloneNotSupportedException {
Mail mail=new Mail();
mail.setContent("初始化模板");
System.out.println("初始化的mail對象:"+mail);
for (int i=0;i<10;i++){
Mail tmpMail= (Mail) mail.clone();
tmpMail.setName("姓名"+i);
tmpMail.setEmailAddress("郵箱"+i);
tmpMail.setContent("郵件內容。。。");
MailUtil.sendMail(tmpMail);
System.out.println("克隆的mail對象:"+tmpMail);
}
MailUtil.saveOriginalContent(mail);
}
//結果:@符號後面的地址不同表示實例化的mail對象不同.
Mail class constructor
初始化的mail對象:Mail{name='null', emailAddress='null', content='初始化模板'}com.zk.javatest.prototype.Mail@1540e19d
clone 方法
克隆的mail對象:Mail{name='姓名0', emailAddress='郵箱0', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@677327b6
clone 方法
克隆的mail對象:Mail{name='姓名1', emailAddress='郵箱1', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@14ae5a5
clone 方法
克隆的mail對象:Mail{name='姓名2', emailAddress='郵箱2', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@7f31245a
clone 方法
克隆的mail對象:Mail{name='姓名3', emailAddress='郵箱3', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@6d6f6e28
clone 方法
克隆的mail對象:Mail{name='姓名4', emailAddress='郵箱4', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@135fbaa4
clone 方法
克隆的mail對象:Mail{name='姓名5', emailAddress='郵箱5', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@45ee12a7
clone 方法
克隆的mail對象:Mail{name='姓名6', emailAddress='郵箱6', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@330bedb4
clone 方法
克隆的mail對象:Mail{name='姓名7', emailAddress='郵箱7', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@2503dbd3
clone 方法
克隆的mail對象:Mail{name='姓名8', emailAddress='郵箱8', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@4b67cf4d
clone 方法
克隆的mail對象:Mail{name='姓名9', emailAddress='郵箱9', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@7ea987ac
保存原始內容:初始化模板
原型模式中的淺克隆與深克隆
先來看看淺克隆
//新建類Pig
public class Pig implements Cloneable{
private String name;
private Date birthday;
public Pig(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Pig{" + "name='" + name + '\''
+ ", birthday=" + birthday + '}'+super.toString();
}
}
//調用
public static void main(String[] a) throws CloneNotSupportedException {
Date birthday=new Date(0L);
Pig pig1=new Pig("佩奇",birthday);
Pig pig2= (Pig) pig1.clone();
System.out.println(pig1);
System.out.println(pig2);
pig1.getBirthday().setTime(666666666666L);
System.out.println("修改後:"+pig1);
System.out.println("修改後:"+pig2);
}
//結果:
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.zk.clone.Pig@4c75cab9
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.zk.clone.Pig@1ef7fe8e
修改後:Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991}com.zk.clone.Pig@4c75cab9
修改後:Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991}com.zk.clone.Pig@1ef7fe8e
從運行結果可以看出,修改了pig1對象的birthday屬性,pig2對象的birthday屬性也被修改了。看看下面這張調試過程的圖。
雖然pig1與pig1克隆出的對象pig2是兩個不同的對象,但是對象裏面的birthday屬性是引用的是同一個對象。因此也就看到了上面的兩個birthday都被修改了。
這就是淺克隆。
下面看看深克隆:修改clone方法。
public class Pig implements Cloneable{
private String name;
private Date birthday;
public Pig(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//深克隆
Pig pig= (Pig) super.clone();
//將引用對象Date也克隆
pig.birthday=(Date) pig.birthday.clone();
return pig;
}
@Override
public String toString() {
return "Pig{" + "name='" + name + '\''
+ ", birthday=" + birthday + '}'+super.toString();
}
}
//運行結果:
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.zk..clone.Pig@6d6f6e28
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.zk..clone.Pig@135fbaa4
修改後:Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991}com.zk.clone.Pig@6d6f6e28
修改後:Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.zk.clone.Pig@135fbaa4
此時修改pig1對象的birthday屬性不會影響pig2的birthday屬性。
因此,使用原型模式時,一定要注意淺克隆與深克隆,否則很容易導致bug。
原型模式破壞單例模式
//餓漢式單例模式,實現Cloneable接口
public class HungrySingleton implements Serializable,Cloneable{
private HungrySingleton(){
if (hungrySingleton!=null){
throw new RuntimeException("單例模式的構造器禁止反射");
}
}
private final static HungrySingleton hungrySingleton=new HungrySingleton();
public static HungrySingleton getInstance() {
return hungrySingleton;
}
private Object readResolve(){
return hungrySingleton;
}
//重寫此方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//調用
HungrySingleton hungrySingleton=HungrySingleton.getInstance();
Method method=hungrySingleton.getClass().getDeclaredMethod("clone");
method.setAccessible(true);
HungrySingleton cloneHungrySingleton= (HungrySingleton) method.invoke(hungrySingleton);
System.out.println(hungrySingleton);
System.out.println(cloneHungrySingleton);
//結果
com.zk.javatest.prototype.HungrySingleton@677327b6
com.zk.javatest.prototype.HungrySingleton@14ae5a5
從運行結果可以看出,兩個實例對象不相同,單例模式被破壞。那如果防止這種破壞呢?很簡單,重寫clone()方法即可。
@Override
protected Object clone() throws CloneNotSupportedException {
return getInstance();
}