對於某些崗位來講,工做週報的內容會大同小異,若是用戶每次都須要從空白的週報進行輸入無疑會浪費用戶不少的時間,若是週報可以按照用戶的自定義來生成模板,或者從已有模板修改小部分獲得新模板,這樣用戶的輸入效率會大大提升。原型模式正是爲解決這類問題而生。java
原型模式:使用原型實例指定建立對象的種類,而且經過克隆這些原型建立新的對象。apache
原型模式是一種對象建立型模式。編程
原型模式的工做原理很簡單,將一個原型對象傳給那個要發動建立的對象,這個要發動建立的對象經過請求原型對象克隆本身來實現建立過程。原型模式是一種另類的建立型模式,建立克隆對象的工廠就是原型類自身,工廠方法由克隆方法實現。數組
經過克隆方法建立的對象是全新的對象,它們在內存中擁有新的地址,一般對克隆多產生的對象進行的修改不會對原型對象形成任何的影響,每個克隆的對象都是相互獨立的,經過不一樣的方式對克隆對象進行修改以後,能夠獲得一系列類似但不徹底相同的對象。微信
Prototype
(抽象原型類):聲明克隆方法的接口,是全部具體原型類的公共父類,能夠是抽象類也能夠是接口,還能是具體實現類ConcretePrototypr
(具體原型類):實如今抽象原型類中聲明的克隆方法,在克隆方法中返回本身的一個克隆對象Client
(客戶類):讓一個原型對象克隆自身從而建立一個新的對象,在客戶類中只須要直接實例化或經過工廠方法等方式建立一個原型對象,再經過調用該對象的克隆方法便可獲得多個相同的對象。clone
的方法clone
clone
方法獲取多個對象這裏定義爲接口:網絡
interface Prototype { Prototype clone(); String getAttr(); void setAttr(String attr); }
實現抽象原型接口,核心在於如何實現clone
,在Java中clone
一般有兩種實現方式:ide
clone()
方法通用的克隆實現方法是在具體原型類的克隆方法中實例化一個與自身類型相同的對象並將其返回,並將相關的參數傳入新建立的對象中,保證成員變量相同。函數
代碼以下:工具
class ConcretePrototype implements Prototype { private String attr; @Override public String getAttr() { return this.attr; } @Override public void setAttr(String attr) { this.attr = attr; } @Override public Prototype clone() { Prototype = new ConcretePrototype(); prototype.setAttr(attr); return prototype; } }
clone
java.lang.Object
提供了一個clone()
,能夠將一個Java對象克隆一份,利用clone()
能夠直接將對象克隆一份,可是必須實現Cloneable
接口,不然clone()
時會拋出CloneNotSupportedException
。測試
代碼以下:
class ConcretePrototype implements Prototype,Cloneable { private String attr; public String getAttr() { return this.attr; } public void setAttr(String attr) { this.attr = attr; } public Prototype clone() { Object object = null; try { object = super.clone(); } catch (Exception e) { e.printStacktrace(); } return (Prototype)object; } }
通常而言,Java中的clone()
知足:
x
都有x.clone() != x
,也就是克隆的對象與原型對象不是同一個對象x
都有x.clone().getClass() == x.getClass()
,即克隆對象與原型對象的類型同樣x
的equals()
定義恰當,那麼x.clone().equals(x)
應該成立具體實現步驟以下:
clone()
,並聲明爲public
clone()
中調用super.clone()
Cloneable
接口客戶類針對抽象原型類編程,經過實例化獲取具體原型後,調用其中的clone
進行克隆:
public class Test { public static void main(String[] args) { Prototype prototype1 = new ConcretePrototype(); prototype1.setAttr("test"); Prototype prototype2 = prototype1.clone(); System.out.println(prototype1.getAttr() == prototype2.getAttr()); System.out.println(prototype1 == prototype2); } }
開發一個工做週報系統,工做週報的內容都大同小異,只有一些小地方存在差別,可是系統每次默認建立的都是空白報表,用戶不斷複製粘貼來填寫重複內容。使用原型模式對其進行優化,快速建立相同或相似的工做週報。
設計以下:
Object
)WeeklyLog
代碼以下:
public class Test { public static void main(String[] args) { WeeklyLog weeklyLog1 = new WeeklyLog(); weeklyLog1.setContent("content"); weeklyLog1.setName("Weekly log 1"); weeklyLog1.setDateTime(LocalDateTime.now()); System.out.println(weeklyLog1.getName()); System.out.println(weeklyLog1.getContent()); System.out.println(weeklyLog1.getDateTime()); WeeklyLog weeklyLog2 = weeklyLog1.clone(); weeklyLog2.setName("Weekly log 2"); System.out.println(weeklyLog2.getName()); System.out.println(weeklyLog2.getContent()); System.out.println(weeklyLog2.getDateTime()); } } class WeeklyLog implements Cloneable { private String name; private LocalDateTime dateTime; private String content; //getter and setter //... public WeeklyLog clone() { Object obj = null; try { obj = super.clone(); } catch(Exception e) { e.printStackTrace(); } return (WeeklyLog)obj; } }
通常來講,工做週報可能會攜帶附件,使用上面的原型模式來進行工做週報的複製沒有問題,可是附件(通常是另外一個類)不會進行復制。這是由於淺克隆與深克隆的緣由,下面具體來看一下。
在淺克隆中,若是原型對象的成員變量是值類型,將複製一份給克隆對象。(在Java中)值類型包括:
int
double
byte
boolean
char
float
long
short
也就是這些類型的值都會完整複製一份給克隆對象,對於引用類型,則將引用對象的地址複製一份給克隆對象。(在Java中)引用類型就是除了基本類型以外的全部類型,常見的有:
對於引用類型,原型對象與克隆對象指向相同的內存地址,也就是其實並無被複制,而是共享一份地址相同的值。
在Java中能夠經過Object
的clone()
實現淺克隆,也就是上面例子的作法。
在深克隆中,不管變量是值類型仍是引用類型都會完整複製一份給克隆對象。
在Java中實現深克隆能夠經過序列化等方式實現。序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個複製品,而原對象仍然存在於內存中。想要進行序列化必須實現Serializable
接口。
代碼以下:
public class Test { public static void main(String[] args) { WeeklyLog weeklyLog1 = new WeeklyLog(); WeeklyLog weeklyLog2 = null; Attachement attachement = new Attachement(); weeklyLog1.setAttachement(attachement); try { weeklyLog2 = weeklyLog1.deepClone(); } catch(Exception e) { e.printStackTrace(); } System.out.println(weeklyLog1 == weeklyLog2); System.out.println(weeklyLog1.getAttachement() == weeklyLog2.getAttachement()); } } class Attachement implements Serializable { private String name; //getter and setter //... } class WeeklyLog implements Serializable { private String name; private LocalDateTime dateTime; private String content; private Attachement attachement; //getter and setter //... public WeeklyLog deepClone() throws IOException , ClassNotFoundException , OptionalDataException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(this); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (WeeklyLog)objectInputStream.readObject(); } }
固然除了使用ByteArrayOutput/InputStream
以及ObjectInput/OutputStream
外,還能夠利用如下工具類進行深克隆:
org.apache.commons.lang3.SerializationUtils.clone()
:須要實現Serializable
接口Gson
:無需實現Serializable
接口,toJson()+fromJson()
Jackson
:也是無需實現Serializable
接口,readValue()+writeValueAsString()
等原型管理器是將多個原型對象存儲在一個集合中供客戶端使用的專門負責克隆對象的工廠,其中定義了一個集合用於存儲原型對象,若是須要某個原型對象的克隆,能夠經過複製集合中對應的原型對象來獲取。在原型管理器中針對抽象原型類進行編程。
結構圖以下:
平常辦公中會有許多公文須要建立,例如《可行性分析報告》,《立項建議書》,《軟件需求規格說明書》,《項目進展報告》等,爲了提升工做效率須要爲各種公文建立模板,用戶能夠經過這些模板快速建立新的公文,這些公文模板進行統一的管理,系統根據用戶的請求的不一樣生成不一樣的新公文。
首先是抽象原型以及具體原型的代碼:
interface OfficialDocument extends Cloneable { OfficialDocument clone(); void display(); } //可行性分析報告 class FAR implements OfficialDocument { public OfficialDocument clone() { OfficialDocument far = null; try { far = (OfficialDocument)super.clone(); } catch(Exception e) { e.printStackTrace(); } return far; } public void display() { System.out.println("可行性分析報告"); } } //軟件需求規格說明書 class SRS implements OfficialDocument { public OfficialDocument clone() { OfficialDocument srs = null; try { srs = (OfficialDocument)super.clone(); } catch(Exception e) { e.printStackTrace(); } return srs; } public void display() { System.out.println("軟件需求規格說明書"); } }
接着是原型管理器的代碼,使用枚舉單例實現:
enum PrototypeManager { INSTANCE; private Hashtable<String,OfficialDocument> hashtable = new Hashtable<>(); private PrototypeManager() { add("far",new FAR()); add("srs",new SRS()); } public void add(String key,OfficialDocument document) { hashtable.put(key, document); } public OfficialDocument get(String key) { return ((OfficialDocument)hashtable.get(key)).clone(); } }
測試代碼:
public class Test { public static void main(String[] args) { PrototypeManager manager = PrototypeManager.INSTANCE; OfficialDocument document1,document2,document3,document4; document1 = manager.get("far"); document1.display(); document2 = manager.get("far"); document2.display(); System.out.println(document1 == document2); document3 = manager.get("srs"); document3.display(); document4 = manager.get("srs"); document4.display(); System.out.println(document3 == document4); } }
若是以爲文章好看,歡迎點贊。
同時歡迎關注微信公衆號:氷泠之路。