一、引出原型模式
如果我們有一個類(sheep),它裏面有兩個屬性,名稱(name),年齡(age)。現在我們有一個它的實例(s1),我們需要按照這個實例的屬性再去創建兩個對象。
1、Sheep
@Data
public class Sheep {
private String name;
private Integer age;
Sheep(String name,Integer age){
this.name = name;
this.age = age;
}
}
2、main
public class Main {
public static void main(String[] args) {
Sheep s1 = new Sheep("多利",1);
Sheep s2 = new Sheep(s1.getName(),s1.getAge());
Sheep s3 = new Sheep(s1.getName(),s1.getAge());
}
}
總結:
- 這樣寫看起來還好不麻煩也沒有問題
- 但是如果這類裏面有100個屬性呢?(我們寫的代碼會很多)
- 或者我們現在對這個類再添加幾個屬性呢?(我們需要修改的地方很多)
二、原型模式
原型模式 : 用原型實例指定創建對象的種類,並且通過拷貝這些原型,創建新的對象。
也就是我們使用clone方法進行對象的創建,我們對上面的 sheep 類新增clone方法。
1、改造後的sheep類
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class Sheep implements Cloneable{
private String name;
private Integer age;
Sheep(String name,Integer age){
this.name = name;
this.age = age;
}
@Override
protected Sheep clone() {
Object obj = null;
try {
obj = super.clone();
}catch (Exception e){
e.printStackTrace();
}
return (Sheep)obj;
}
}
2、main 測試
public class Main {
public static void main(String[] args) {
Sheep s1 = new Sheep("多利",1);
Sheep s2 = s1.clone();
Sheep s3 = s1.clone();
System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // false
}
}
總結
- 使用clone方法去創建對象,就解決上面的問題了。
- 但其我們的clone方法是一個淺拷貝,如果類裏面有引入屬性,那麼創建出來的對象的引用都是指向一個的。
我們對 淺拷貝 進行一個測試
1、在上面的 sheep 類裏面新增一個 屬性
private Sheep friend;
2、測試
public class Main {
public static void main(String[] args) {
Sheep s1 = new Sheep("多利",1);
s1.setFriend(new Sheep("小道仙",2));
Sheep s2 = s1.clone();
Sheep s3 = s1.clone();
System.out.println(s1.getFriend().getName() + " " + s2.getFriend().getName() + " " + s3.getFriend().getName());
s2.getFriend().setName("王哈哈");
System.out.println(s1.getFriend().getName() + " " + s2.getFriend().getName() + " " + s3.getFriend().getName());
}
// 打印結果如下
小道仙 小道仙 小道仙
王哈哈 王哈哈 王哈哈
}
三、淺拷貝、深拷貝
淺拷貝 :在拷貝對象的時候對引用類型,沒有從新創建一個對象,而是指向之前對象的引用。
深拷貝 :解決淺拷貝存在的問題,我們去爲引用類型創建一個新的對象。
我們使用深拷貝去解決淺拷貝的問題
方式一:使用多次clone
@Override
protected Sheep clone() {
Sheep obj = null;
try {
obj = (Sheep)super.clone();
if (this.getFriend() != null){
Sheep clone = this.getFriend().clone();
obj.setFriend(clone);
}
}catch (Exception e){
e.printStackTrace();
}
return obj;
}
- 從上面的代碼我們可以看到所謂多次克隆,也就是對引用類型再進行一次克隆。
- 優點:它寫起來和理解都很簡單。
- 缺點:如果引用類型過多那麼寫起來很麻煩,後續如果 新增/刪除 引用類型,我們還需要修改 clone 方法。
方式二:使用序列化和反序列化 (注:需要實現序列化接口 implements Serializable)
@Override
protected Sheep clone() {
Sheep obj = null;
// 創建流對象
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);
obj = (Sheep)ois.readObject();
return obj;
}catch (Exception e){
e.printStackTrace();
return null;
}finally {
// 關閉流
try {
bos.close();
oos.close();
bis.close();
ois.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
- 使用序列化優勢很明顯,後續 新增/刪除 引用類型,我們不需要修改 clone 方法,只不過寫起來稍微複雜一點。但是推薦使用這個。
兩種方法的測試代碼如下,結果也都一樣
public class Main {
public static void main(String[] args) {
Sheep s1 = new Sheep("多利",1);
s1.setFriend(new Sheep("小道仙",2));
Sheep s2 = s1.clone();
Sheep s3 = s1.clone();
System.out.println(s1.getFriend().getName() + " " + s2.getFriend().getName() + " " + s3.getFriend().getName());
s2.getFriend().setName("王哈哈");
System.out.println(s1.getFriend().getName() + " " + s2.getFriend().getName() + " " + s3.getFriend().getName());
// 打印結果
小道仙 小道仙 小道仙
小道仙 王哈哈 小道仙
}
}