模式定義
用原型實例指定創建對象的種類,并通過拷貝這些原型創建新的對象。
模式結構
模式結構
代碼實現
public interface Prototype {
Prototype clone();
}
public class ConcretePrototype1 implements Prototype {
public Prototype clone() {
return new ConcretePrototype1();
}
}
public class ConcretePrototype2 implements Prototype {
public Prototype clone() {
return new ConcretePrototype2();
}
}
@AllArgsConstructor
public class Client {
private Prototype prototype;
public void operation(){
Prototype newPrototype = prototype.clone();
}
}
模式的優缺點
優點
創建對象的性能高
使用原型模式創建對象比直接new一個對象在性能上要好的多,因為Object類的clone方法是一個本地方法,它直接操作內存中的二進制流,特別是復制大對象時,性能的差別非常明顯。簡化對象的創建
缺點
克隆的對象的成員變量包含引用類型(String除外)需要特殊處理
逃避構造方法的約束
必須實現Cloneable接口
深拷貝和淺拷貝
發生深拷貝的有Java中的8中基本類型以及它們包裝類型,另外還有String類型。其余的都是淺拷貝。
淺拷貝
對值類型的成員變量進行值的復制,對引用類型的成員變量只復制引用,不復制引用的對象。
@Data
@AllArgsConstructor
public class Student implements Cloneable{
private String studentName;
private Teacher teacher;
@Override
protected Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
}
@Data
public class Teacher implements Serializable, Cloneable {
private static final long UID = 6948989635489677685L;
private String name;
public Teacher(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Teacher teacher;
teacher = (Teacher) super.clone();
return teacher;
}
}
@Data
public class StudentLow implements Serializable, Cloneable {
private static final long UID = 6948989635489677685L;
private String studentName;
private Teacher teacher;
public StudentLow(String studentName, Teacher teacher) {
this.studentName = studentName;
this.teacher = teacher;
}
@Override
protected Object clone() throws CloneNotSupportedException {
StudentLow student;
student = (StudentLow) super.clone();
return student;
}
}
public class Client {
public static void main(String[] args) throws Exception {
Teacher teacher = new Teacher("snail");
StudentLow student1 = new StudentLow("wjk", teacher);
StudentLow student2 = (StudentLow) student1.clone();
student2.getTeacher().setName("snail改變");
System.out.println(student1.getTeacher().getName());
System.out.println(student2.getTeacher().getName());
}
}
//運行結果(修改克隆的Student的成員變量Teacher的成員變量name導致被克隆的對象的也改變)
snail
snail改變
深拷貝
對值類型的成員變量進行值的復制,對引用類型的成員變量也進行引用對象的復制。
//將上面代碼StudentLow類使用StudentDeep類替換
@Data
public class StudentDeep implements Serializable, Cloneable {
private static final long UID = 6948989635489677685L;
private String studentName;
private Teacher teacher;
public StudentDeep(String studentName, Teacher teacher) {
this.studentName = studentName;
this.teacher = teacher;
}
@Override
protected Object clone() throws CloneNotSupportedException {
StudentDeep student;
student = (StudentDeep) super.clone();
//繼續克隆Teacher
student.setTeacher((Teacher) this.teacher.clone());
return student;
}
/**
* 使用序列化實現深拷貝
*/
/*public Object deepClone() {
try {
//將對象寫到流里
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
//從流里讀出來
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return (oi.readObject());
} catch (Exception e) {
return null;
}
}*/
}
思考
模式本質:克隆生成對象。
開發中的應用場景
如果一個系統想要獨立于它想要使用的對象時,可以使用原型模式,讓系統只面向接口編程,在系統需要新的對象的時候,可以通過克隆來得到。
如果需要實例化的類事在運行時刻動態指定是,可以使用原型模式,通過克隆原型來得到需要的實例。
注意
- 使用原型模式不會調用類的構造方法.
對象的復制是通過調用Object類的clone方法來完成的,它直接在內存中復制數據,因此不會調用到類的構造方法。不但構造方法中的代碼不會執行,甚至連訪問權限都對原型模式無效。還記得單例模式嗎?單例模式中,只要將構造方法的訪問權限設置為private型,就可以實現單例。但是clone方法直接無視構造方法的權限,所以,單例模式與原型模式是沖突的,在使用時要特別注意。