一、原型模式
原型模式(Prototype Pattern)是用於創建重複的對象,同時又能保證性能。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。
這種模式是實現了一個原型接口,該接口用於創建當前對象的克隆。當直接創建對象的代價比較大時,則採用這種模式。例如,一個對象需要在一個高代價的數據庫操作之後被創建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新數據庫,以此來減少數據庫調用。
1、原型模式舉例(參考菜鳥教程)
public abstract class Shape implements Cloneable {
private String id;
protected String type;
abstract void draw();
public String getType(){
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
這是需要創建的類的基類(父類),必須實現Cloneable接口才能進行clone。
public class Square extends Shape {
public Square(){
type = "Square";
}
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class Circle extends Shape {
public Circle(){
type = "Circle";
}
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
這個Square類就是我們需要大量重複使用的類,這裏需要說明一點,如果你需要複用的類和這個Square一樣簡單,或者獲取非常容易。請不要使用原型模式,如果類過於簡單使用clone的效率反而會比new來的低。
import java.util.Hashtable;
public class ShapeCache {
private static Hashtable<String, Shape> shapeMap
= new Hashtable<String, Shape>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
// 對每種形狀都運行數據庫查詢,並創建該形狀
// shapeMap.put(shapeKey, shape);
// 例如,我們要添加兩種形狀
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);
Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(),square);
}
}
ShapeCache就是我們用來創建方法的工具類。loadCache先用new的方式創建了基本類,這個類是用來以後克隆使用的。
ShapeCache.loadCache();
Shape clonedShape = (Shape) ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.getType());
Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType());
以上是最終調用的方法,可以發現,我們獲取的方法並不是new出來的也不是反射出來的而是clone出來的。
2、原型模式特點
優點: 1、性能提高。 2、逃避構造函數的約束。
缺點: 1、配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類不是很難,但對於已有的類不一定很容易,特別當一個類引用不支持串行化的間接對象,或者引用含有循環結構的時候。 2、必須實現 Cloneable 接口。
使用場景: 1、資源優化場景。 2、類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等。 3、性能和安全要求的場景。 4、通過 new 產生一個對象需要非常繁瑣的數據準備或訪問權限,則可以使用原型模式。 5、一個對象多個修改者的場景。
二、java實例化對象的方式
- new
- clone
- 反序列化
- 反射
java常見的實例化對象的方式就以上4中,在原型模式中我們使用了new和clone的方式,那我們就來對比一下這兩個方式的區別吧。
1、new
new,這個方法我們再熟悉不過了,new一個實例時,java虛擬機先會調用類的加載,分別通過加載、連接、初始化、使用然後使用完之後卸載的過程,我們new一個類時也一樣,jvm會去判斷這個類是否已經加載過,如果加載過就會去開闢一段堆內存去創建類。然後調用構造方法之後實例化就會成功。new操作符的本意是分配內存。
程序執行到new操作符時, 首先去看new操作符後面的類型,因爲知道了類型,才能知道要分配多大的內存空間。分配完內存之後,再調用構造函數,填充對象的各個域,這一步叫做對象的初始化,構造方法返回後,一個對象創建完畢,可以把他的引用(地址)發佈到外部,在外部就可以使用這個引用操縱這個對象(當然,由於指令的重排序,發佈對象可能在構造函數返回之前。
2、clone
clone在第一步是和new相似的, 都是分配內存,調用clone方法時,分配的內存和源對象(即調用clone方法的對象)相同,然後再使用源對象中對應的各個域,填充新對象的域, 填充完成之後,clone方法返回,一個新的相同的對象被創建,同樣可以把這個新對象的引用發佈到外部。
需要注意的是,和new不同,clone涉及到深拷貝和淺拷貝的問題
情報:
淺複製
被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所考慮的對象,而不復制它所引用的對象。深複製
被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深複製把要複製的對象所引用的對象都複製了一遍。
java克隆需要注意的點
Java的clone()方法
⑴clone方法將對象複製了一份並返回給調用者。一般而言,clone()方法滿足:
①對任何的對象x,都有x.clone() !=x//克隆對象與原對象不是同一個對象
②對任何的對象x,都有x.clone().getClass()= =x.getClass()//克隆對象與原對象的類型一樣
③如果對象x的equals()方法定義恰當,那麼x.clone().equals(x)應該成立。⑵Java中對象的克隆
①爲了獲取對象的一份拷貝,我們可以利用Object類的clone()方法。
②在派生類中覆蓋基類的clone()方法,並聲明爲public。
③在派生類的clone()方法中,調用super.clone()。
④在派生類中實現Cloneable接口。繼承自java.lang.Object類的clone()方法是淺複製
如果要實現深拷貝就需要手動實現clone方法。
static class Body implements Cloneable {
public Head head;
public Body() {
}
public Body(Head head) {
this.head = head;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Body newBody = (Body) super.clone();
newBody.head = (Head) head.clone();
return newBody;
}
}
static class Head implements Cloneable {
public Face face;
public Head() {
}
public Head(Face face) {
this.face = face;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
static class Face implements Cloneable {
public Face() {
}
}
(上述代碼參考)如果我們clone Body類A、B。A.head 和 B.head指向的內存地址不同,但是A.head.face 和 B.head.face指向的內存是一樣的。
所以,實現clone是比較困難的,因爲子子孫孫無窮盡也。所有的內部引用都需要單獨重寫clone方法。
三、總結
java的每一種實例化方法都有它不同的作用,new是最基礎的加載,反射是獲取該類的所有內容,clone可以減少CPU開支,序列化用來存儲實例的狀態,這個大家可以自行體會一下哦。