一、引言
欲言又止,
二、克隆羊
假設現在我們有一個對象,需要拷貝新的對象出來,以下代碼是最簡單粗暴的方式了。 但是如果這個對象有很多屬性呢? 那豈不是太麻煩了,針對這種情況就可以使用我們的原型模式來實現。
原型模式是指:用原型實例指定創建對象的種類,並且通過拷貝原型,從而創建新的對象。
原型模式是一種創建型的設計模式,允許一個對象在創建另外一個可定製的對象,無需知道創建的細節。其實也就是說白了把拷貝的具體實現封裝在類裏面,外部只需要使用對應的方法則可以實現拷貝的效果。
public static void main(String[] args) {
// 創建一個對象
Sheep sheep = new Sheep("Tom", 1);
// 根據上面那個對象,創建新的對象
Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge());
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge());
Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge());
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
}
三、使用原型模式實現克隆羊
針對上面剛剛那個案例,使用原型模式進行改進。
步驟一:在Sheep這個類需要實現Cloneable的接口,其實這個Cloneable是一個標記接口,然後在類中需要重寫Object的clone方法,然後通過類調用這個clone方法,從而達到克隆的效果。
如果不實現這個接口,則會拋出CloneNotSupportedException克隆不被支持的異常。
步驟二:重寫Object的clone方法,使用時直接調用即可。
/**
* @Auther: IT賤男
* @Date: 2019/8/5 15:21
* @Description: 克隆羊 - 原型模式
*/
@Data
public class Sheep implements Cloneable {
private String name;
private int age;
public Sheep(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Sheep clone() {
try {
// 這裏默認調用父類的實現方法即可,默認是淺拷貝
return (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public static void main(String[] args) {
Sheep sheep = new Sheep("Tom", 1);
// 這裏直接調用clone方法即可
Sheep sheep1 = sheep.clone();
Sheep sheep2 = sheep.clone();
System.out.println(sheep == sheep1);
System.out.println(sheep2 == sheep1);
}
四、理解深拷貝和淺拷貝
淺拷貝:
對於基本數據類型的成員變量,淺拷貝會進行值傳遞,也就是將屬性值賦值給新對象。
對於成員是引用類型,比如數組、對象等,那麼淺拷貝會進行引用傳遞。也就是將成員等引用值複製一個給新對象,實際上兩個對象都是指向同一個實例,在這種情況下修改了一個對象中得值,另外一個對象也會隨之而改變。
深拷貝:
深拷貝不僅僅複製了所有的基本數據類型,爲所有的引用數據類型的成員變量申請存儲空間。 也就是說引用成員不再拷貝引用地址,而是整個對象進行拷貝。
五、深拷貝兩種實現方式
現在我們在Sheep類中新增一個對象叫Friend,並且添加對應的構造方法。
/**
* @Auther: IT賤男
* @Date: 2019/8/5 15:21
* @Description: 克隆羊 - 原型模式
*/
@Data
public class Sheep implements Cloneable {
private String name;
private int age;
private Friend friend;
public Sheep(String name, int age, Friend friend) {
this.name = name;
this.age = age;
this.friend = friend;
}
@Override
protected Sheep clone() {
try {
return (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public static void main(String[] args) {
Sheep sheep = new Sheep("Tom", 1,new Friend("Miqi"));
// 這裏採用的是默認淺拷貝,所以拷貝出來的friend對象是同一個
Sheep sheep1 = sheep.clone();
System.out.println(sheep.getFriend() == sheep1.getFriend());
}
實現方式一:首先我們需要將Friend這個類,實現默認的clone方法,然後重寫Sheep中的clone方法。
這種方式雖然可以實現,如果引用對象很多,是不推薦使用的
/**
* @Auther: IT賤男
* @Date: 2019/8/20 11:25
* @Description:
*/
public class Friend implements Cloneable {
public Friend(String name) {
this.name = name;
}
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/**
* 深拷貝實現方式一
* @return
*/
@Override
protected Sheep clone() {
try {
Sheep sheep = (Sheep) super.clone();
// 這裏需要調用friend的clone方法,然後複製給新克隆出來的對象
sheep.setFriend((Friend) friend.clone());
return sheep;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
實現方式二:方式一雖然說可以實現,但是大家也看出來了,引用對象需要重新賦值,那如果引用對象很多,那也很麻煩,所以來看看第二種方式,這種方式比較省事。
首先需要將引用對象實現序列化接口,然後採用序列化的方式來實現深拷貝。
/**
* @Auther: IT賤男
* @Date: 2019/8/20 11:25
* @Description:
*/
public class Friend implements Serializable{
private static final long serialVersionUID = -3019656355622657141L;
public Friend(String name) {
this.name = name;
}
private String name;
}
/**
* @Auther: IT賤男
* @Date: 2019/8/5 15:21
* @Description: 克隆羊 - 原型模式
*/
@Data
public class Sheep implements Cloneable, Serializable {
private static final long serialVersionUID = -270095030076915889L;
private String name;
private int age;
private Friend friend;
public Sheep(String name, int age, Friend friend) {
this.name = name;
this.age = age;
this.friend = friend;
}
/**
* 使用序列化的方式實現深拷貝
* @return
*/
@Override
protected Sheep clone() {
Sheep sheep = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
sheep = (Sheep) ois.readObject();
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return sheep;
}
}
六、原型模式優缺點
優點:如果需要複製一個新的對象時,可以利用原型模式簡化對象的創建過程,同時也可以提高效率。
缺點:需要爲每一個類配備一個克隆方法,如果對已有的代碼類進行改造,需要修改源碼,違背了OCP原則。