設計模式系列—原型模式
前言
- 23種設計模式速記
- 單例(singleton)模式
- 工廠方法(factory method)模式
- 抽象工廠(abstract factory)模式
- 建造者/構建器(builder)模式
23種設計模式快速記憶的請看上面第一篇,本篇和大家一起來學習原型模式,在學習原型模式之前我們需要先認識下淺拷貝和深拷貝這兩個概念。
淺拷貝和深拷貝
淺拷貝
淺拷貝是按位拷貝對象,它會創(chuàng)建一個新對象,這個對象有著原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值;如果屬性是內存地址(引用類型),拷貝的就是內存地址 ,因此如果其中一個對象改變了這個地址,就會影響到另一個對象。
復制了對象的引用地址,兩個對象指向同一個內存地址,所以修改其中任意的值,另一個值都會隨之變化。
深拷貝
深拷貝會拷貝所有的屬性,并拷貝屬性指向的動態(tài)分配的內存。當對象和它所引用的對象一起拷貝時即發(fā)生深拷貝。深拷貝相比于淺拷貝速度較慢并且花銷較大。
將對象及值復制過來,兩個對象修改其中任意的值另一個值不會改變。
模式定義
用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象。
原型模式其實就是一個對象在創(chuàng)建另一個可定制的對象,而且不需要指定任何創(chuàng)建的細節(jié)。Java提供了Coneable接口,其中有一個唯一方法Clone(),實現(xiàn)這個接口就可以完成原型模式了。
實例說明
淺拷貝案例
聲明 User 實體類,需要實現(xiàn) Cloneable 接口,并覆寫 clone() 方法。
User 屬性包括基礎數(shù)據(jù)類型和引用數(shù)據(jù)類型,方便演示
- package com.niuh.designpattern.prototype;
- /**
- * 用戶信息
- */
- public class User implements Cloneable {
- // 基礎數(shù)據(jù)類型
- private int id;
- private String name;
- private String sex;
- private String pwd;
- // 引用數(shù)據(jù)類型
- private BaseInfo baseInfo;
- public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) {
- this.id = id;
- this.name = name;
- this.sex = sex;
- this.pwd = pwd;
- this.baseInfo = baseInfo;
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getSex() {
- return sex;
- }
- public void setSex(String sex) {
- this.sex = sex;
- }
- public String getPwd() {
- return pwd;
- }
- public void setPwd(String pwd) {
- this.pwd = pwd;
- }
- public BaseInfo getBaseInfo() {
- return baseInfo;
- }
- public void setBaseInfo(BaseInfo baseInfo) {
- this.baseInfo = baseInfo;
- }
- @Override
- public String toString() {
- return "hashCode: " + super.hashCode() + ", User{" +
- "id=" + id +
- ", name='" + name + '\'' +
- ", sex='" + sex + '\'' +
- ", pwd='" + pwd + '\'' +
- ", baseInfo=" + baseInfo +
- '}';
- }
- @Override
- protected User clone() throws CloneNotSupportedException {
- return (User) super.clone();
- }
- }
- package com.niuh.designpattern.prototype;
- import java.util.Date;
- /**
- * 基礎類
- */
- public class BaseInfo {
- private String desc;
- // .......
- public BaseInfo(String desc) {
- this.desc = desc;
- }
- public String getDesc() {
- return desc;
- }
- public void setDesc(String desc) {
- this.desc = desc;
- }
- @Override
- public String toString() {
- return "BaseInfo{" +
- "desc=" + desc +
- '}';
- }
- }
- package com.niuh.designpattern.prototype;
- /**
- * 原型設計模式
- */
- public class PrototypePattern {
- public static void main(String[] args) throws CloneNotSupportedException {
- BaseInfo baseInfo = new BaseInfo("張三");
- User user1 = new User(1, "張三", "男", "123456", baseInfo);
- // new User ......
- // 克隆機制
- User user2 = user1.clone();
- user2.setId(2);
- user2.setName("李四");
- BaseInfo baseInfo1 = user2.getBaseInfo();
- baseInfo1.setDesc("李四");
- System.out.println(user1);
- System.out.println(user2);
- }
- }
輸出結果如下:
由輸出的結果可見,通過 user1.clone() 拷貝對象后得到的 user2,和 user1 是兩個不同的對象,HashCode 值不一樣。user1 和 user2 的基礎數(shù)據(jù)類型的修改互不影響,而引用類型 baseInfo 修改后是會有影響的。
深拷貝案例
通過上面的例子可以看到,淺拷貝會帶來數(shù)據(jù)安全方面的隱患,例如我們只是想修改了 user2 的 baseInfo,但是 user1 的 baseInfo 也被修改了,因為它們都是指向的同一個地址。所以,此種情況下,我們需要用到深拷貝。
深拷貝,在拷貝引用類型成員變量時,為引用類型的數(shù)據(jù)成員另辟了一個獨立的內存空間,實現(xiàn)真正內容上的拷貝。
對于 User 的引用類型的成員變量 BaseInfo ,需要實現(xiàn) Cloneable 并重寫 clone() 方法。
- package com.niuh.designpattern.prototype;
- import java.util.Date;
- /**
- * 基礎類
- */
- public class BaseInfo implements Cloneable {
- private String desc;
- // .......
- public BaseInfo(String desc) {
- this.desc = desc;
- }
- public String getDesc() {
- return desc;
- }
- public void setDesc(String desc) {
- this.desc = desc;
- }
- @Override
- public String toString() {
- return "BaseInfo{" +
- "desc=" + desc +
- '}';
- }
- @Override
- protected BaseInfo clone() throws CloneNotSupportedException {
- //BaseInfo 如果也有引用類型的成員屬性,也應該和 User 類一樣實現(xiàn)
- return (BaseInfo) super.clone();
- }
- }
在 User 的 clone() 方法中,需要拿到拷貝自己后產生的新的對象,然后對新的對象的引用類型再調用拷貝操作,實現(xiàn)對引用類型成員變量的深拷貝。
- package com.niuh.designpattern.prototype;
- /**
- * 用戶信息
- */
- public class User implements Cloneable {
- // 基礎數(shù)據(jù)類型
- private int id;
- private String name;
- private String sex;
- private String pwd;
- // 引用數(shù)據(jù)類型
- private BaseInfo baseInfo;
- public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) {
- this.id = id;
- this.name = name;
- this.sex = sex;
- this.pwd = pwd;
- this.baseInfo = baseInfo;
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getSex() {
- return sex;
- }
- public void setSex(String sex) {
- this.sex = sex;
- }
- public String getPwd() {
- return pwd;
- }
- public void setPwd(String pwd) {
- this.pwd = pwd;
- }
- public BaseInfo getBaseInfo() {
- return baseInfo;
- }
- public void setBaseInfo(BaseInfo baseInfo) {
- this.baseInfo = baseInfo;
- }
- @Override
- public String toString() {
- return "hashCode: " + super.hashCode() + ", User{" +
- "id=" + id +
- ", name='" + name + '\'' +
- ", sex='" + sex + '\'' +
- ", pwd='" + pwd + '\'' +
- ", baseInfo=" + baseInfo +
- '}';
- }
- @Override
- protected User clone() throws CloneNotSupportedException {
- // 深拷貝
- User user = (User) super.clone();
- user.baseInfo = baseInfo.clone();
- return user;
- }
- }
與上面的使用方式一樣,輸出結果如下:
由輸出結果可見,深拷貝后,不管是基礎數(shù)據(jù)類型還是引用類型的成員變量,修改其值都不會相互造成影響。
序列化機制實現(xiàn)深拷貝
需要在 User 類實現(xiàn) Serializable,成員類型(BaseInfo)也需要實現(xiàn) Serializable 接口。
- package com.niuh.designpattern.prototype;
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- /**
- * 用戶信息
- */
- public class User implements Cloneable , Serializable {
- // 基礎數(shù)據(jù)類型
- private int id;
- private String name;
- private String sex;
- private String pwd;
- // 引用數(shù)據(jù)類型
- private BaseInfo baseInfo;
- public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) {
- this.id = id;
- this.name = name;
- this.sex = sex;
- this.pwd = pwd;
- this.baseInfo = baseInfo;
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getSex() {
- return sex;
- }
- public void setSex(String sex) {
- this.sex = sex;
- }
- public String getPwd() {
- return pwd;
- }
- public void setPwd(String pwd) {
- this.pwd = pwd;
- }
- public BaseInfo getBaseInfo() {
- return baseInfo;
- }
- public void setBaseInfo(BaseInfo baseInfo) {
- this.baseInfo = baseInfo;
- }
- @Override
- public String toString() {
- return "hashCode: " + super.hashCode() + ", User{" +
- "id=" + id +
- ", name='" + name + '\'' +
- ", sex='" + sex + '\'' +
- ", pwd='" + pwd + '\'' +
- ", baseInfo=" + baseInfo +
- '}';
- }
- @Override
- protected User clone() throws CloneNotSupportedException {
- // 深拷貝
- // User user = (User) super.clone();
- // user.baseInfo = baseInfo.clone();
- // return user;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- try (ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream)) {
- oos.writeObject(this);
- } catch (IOException e) {
- e.printStackTrace();
- }
- ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
- try (ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream)) {
- try {
- User user = (User) ois.readObject();
- return user;
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
- }
這個時候并沒有使用Java深拷貝,改變成員屬性Baseinfo,也能保存對象的獨立性。
通過序列化機制來完成深拷貝不推薦使用,因為序列化操作是CPU密集型,解析流是比較消耗性能,速度會比較慢
優(yōu)點
- 可以不耦合具體類的情況下克隆對象
- 避免重復的初始化代碼
- 更方便的構建復雜對象
缺點
- 適用性不是很廣。
- 每一個類必須配備一個克隆方法。
- 配備克隆方法需要對類的功能進行通盤考慮,這對于全新的類不是很難,但對于已有的類不一定很容易,特別當一個類引用不支持串行化的間接對象,或者引用含有循環(huán)結構的時候。
應用場景
當代碼不應該依賴于需要復制的對象的具體類時,請使用Prototype模式。
- 某些結構復雜的對象的創(chuàng)建工作;由于需求的變化,這些對象經常面臨著劇烈的變化,但是他們卻擁有比較穩(wěn)定一致的接口;
- 一般在初始化的信息不發(fā)生變化的情況下,克隆是最好的方法。
源碼中的應用
- #Spring
- org.springframework.beans.factory.support.AbstractBeanDefinition
- #JDK
- java.util.Arrays
- java.util.ArrayList
- ......
ArrayList中的使用
ArrayList也有clone()方法,如下
- 返回一個Object對象,所以在使用此方法的時候要強制轉換。
- ArrayList的本質是維護了一個Object的數(shù)組,所以克隆也是通過數(shù)組的復制實現(xiàn)的,屬于淺復制。
- @Override
- public Object clone() {
- try {
- ArrayList<?> result = (ArrayList<?>) super.clone();
- result.array = array.clone();
- return result;
- } catch (CloneNotSupportedException e) {
- throw new AssertionError();
- }
- }
ArrayList的Clone淺復制的巧妙使用
當你需要使用remove方法移除掉集合中的對象,而非要修改集合中的對象的時候,可以選擇使用。
- //添加兩個元素
- Student stJack=new Student("Jack", 13);
- Student stTom=new Student("Tom", 15);
- list.add(stJack);
- list.add(stTom);
- //克隆
- ArrayList<Student> listCopy=(ArrayList<Student>) list.clone();
- //移除且不修改
- listCopy.remove(1);
- System.out.println(list);
- System.out.println(listCopy);
移除且不修改集合中的元素,只是在List內部的數(shù)組中移除了指向元素的地址,可以放心的使用clone。
PS:以上代碼提交在 Github :https://github.com/Niuh-Study/niuh-designpatterns.git