軟體設計模式——原型模式(Prototype)
定義與結構
原型模式屬於物件建立模式,我們從名字即可看出該原型模式的思想就是將一個物件作為原型,其進行復制、克隆產生和類似的新物件。GOF給它的定義為:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。
Java 中提供了clone()方法,使用Object的clone()方法來實現物件的克隆,所以Prototype 模式實現變得簡單許多。
既然用到clone()方法,不可避免的我們就會涉及到深拷貝和淺拷貝。
淺拷貝:將一個物件複製後,基本資料型別的變數都會重新建立,而引用指向型別,指向的還是原物件所指向的。
深拷貝:將一個物件複製後,不論是基本資料型別還有引用都重新建立的。簡單來說,深拷貝進行了完全徹底的複製,而淺拷貝複製不徹底。
使用克隆方式來建立物件與同樣用來建立物件的工廠模式有什麼不同?前面已經提過工廠模式對新產品的適應能力比較弱:建立新的產品時,就必須修改或者增加工廠角色。而且為了建立產品物件要先額外的建立一個工廠物件。那通過原型模式來建立物件會是什麼樣子呢?
先讓我們來看看原型模式的結構吧。
1) 客戶角色(Client):讓一個原型克隆自己來得到一個新物件。
2) 抽象原型角色(Prototype):實現了自己的clone 方法,扮演這種角色的類通常是抽象類,且它具有許多具體的子類。
3) 具體原型角色(ConcretePrototype):被複制的物件,為抽象原型角色的具體子類。
類圖:
按照定義客戶角色不僅要負責使用物件,而且還要負責物件原型的生成和克隆。這樣造成客戶角色分工就不是很明確,所以我們把物件原型生成和克隆功能單拿出來放到一個原型管理器中。原型管理器維護了已有原型的清單。客戶在使用時會向原型管理器發出請求,而且可以修改原型管理器維護的清單。這樣客戶不需要編碼就可以實現系統的擴充套件。
類圖表示如下:
原型模式的實現
我們將建立一個抽象類 Shape 和擴充套件了 Shape 類的實體類。下一步是定義類 ShapeCache,該類把 shape 物件儲存在一個 Hashtable 中,並在請求的時候返回它們的克隆。
PrototypPatternDemo,我們的演示類使用 ShapeCache 類來獲取 Shape 物件。
//抽象原型角色--Prototype
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;
}
}
//具體原型角色--ConcretePrototype1
public class Rectangle extends Shape {
public Rectangle() {
type = "Rectangle";
}
@Override
void draw() {
System.out.println("Inside Rectangle::draw() method");
}
}
//具體原型角色--ConcretePrototype2
public class Square extends Shape{
public Square() {
type = "Square";
}
@Override
void draw() {
System.out.println("Inside Square::draw() method.");
}
}
//具體原型角色--ConcretePrototype3
public class Circle extends Shape{
public Circle() {
type = "Circle";
}
@Override
void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
//原型管理器--PrototypeManager
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);
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(), rectangle);
}
}
//客戶角色--Client
public class Client {
public static void main(String[] args)
{
ShapeCache.loadCache();
Shape cloneShape = (Shape)ShapeCache.getShape("1");
System.out.println("Shape:"+cloneShape.getType());
Shape clonedShape2 = (Shape)ShapeCache.getShape("2");
System.out.println("Shape:"+clonedShape2.getType());
Shape clonedShape3 = (Shape)ShapeCache.getShape("3");
System.out.println("Shape:"+clonedShape2.getType());
}
}
執行結果:
Shape:Circle
Shape:Square
Shape:Square
原型模式的分析
原型模式與其它建立型模式有著相同的特點:它們都將具體產品的建立過程進行包裝,使得客戶對建立不可知。
如果一個物件的建立總是由幾種固定元件不同方式組合而成;如果物件之間僅僅例項屬性不同。將不同情況的物件快取起來,直接克隆使用。也許這比採用傳遞引數重新new 一個物件要來的快一些。
你也許已經發現原型模式與工廠模式有著千絲萬縷的聯絡:原型管理器不就是一個工廠麼。當然這個工廠經過了改進(例如上例採用了java 的反射機制),去掉了像抽象工廠模式或者工廠方法模式那樣繁多的子類。因此可以說原型模式就是在工廠模式的基礎上加入了克隆方法。
也許你要說:我實在看不出來使用 clone 方法產生物件和new 一個物件有什麼區別;
原型模式使用clone 能夠動態的抽取當前物件執行時的狀態並且克隆到新的物件中,新物件就可以在此基礎上進行操作而不損壞原有物件;而new 只能得到一個剛初始化的物件,而在實際應用中,這往往是不夠的。
特別當你的系統需要良好的擴充套件性時,在設計中使用原型模式也是很必要的。比如說,你的系統可以讓客戶自定義自己需要的類別,但是這種類別的初始化可能需要傳遞多於已有類別的引數,而這使得用它的類將不知道怎麼來初始化它(因為已經寫死了),除非對類進行修改。可見 clone 方法是不能使用建構函式來代替的。
總結
任何模式都是存在缺陷的。原型模式主要的缺陷就是每個原型必須含有clone 方法,在已有類的基礎上來新增clone 操作是比較困難的;而且當內部包括一些不支援copy 或者迴圈引用的物件時,實現就更加困難了。