java設計模式—原型模式

燕雀安知毛驢之志發表於2021-04-15

Java原型模式

1、概述

  啥是原型模式?

  原型模式屬於設計模式中的建立型中的一員,

  原型模式:使用原型例項指定待建立物件的型別,並且通過複製這個原型來建立新的物件!

  說大白話就是自己複製自己,通過原生物件複製出一個新的物件,這兩個物件結構相同且相似;

  需要注意的是,原型物件自己不僅是個物件還是個工廠!並且通過克隆方式建立的物件是全新的物件,它們都是有自己的新的地址,通常對克隆模式所產生的新物件進行修改,是不會對原型物件造成任何影  響的,每一個克隆物件都是相對獨立的,通過不同的方式對克隆物件進行修改後,可以的到一系列相似但不完全相同的物件。

2、原型UML圖

 

 

3、深克隆與淺克隆

  原型模式中又可細分為淺克隆和深克隆;

  淺克隆:在淺克隆中,如果原型物件的成員變數是值型別(八大基本型別,byte,short,int,long,char,double,float,boolean).那麼就直接複製,如果是複雜的型別,(如列舉、物件)就只複製對應的記憶體地址。

  深克隆:就是什麼都是單獨的!全部複製,然後各自獨立,修改克隆物件對於原型物件沒有任何影響,對於深克隆具體克隆多深取決於業務需求和類結構設計。

4、程式碼案例

  4.1、先來一個簡單小案例熱熱身

  這個淺克隆比較簡單,讓我們由淺入深的學習原型模,先看下這個有助於理解深克隆,廢話不多說直接上程式碼

package pattern.prototype.demo;
/**
 * 蘋果原型類,這就是我們要複製的物件類
 * 要想克隆一個例項必須要實現Cloneable介面,否則會丟擲異常(java.lang.CloneNotSupportedException),
 * @author ningbeibei
 */
public class Apple implements Cloneable{
    //蘋果品種
    public String variety;
    //數量
    public int no;
    
    //新增克隆這個物件的方法,
    public Apple cloneApple() {
        Object obj =null;
        try {
            obj = super.clone();
            return (Apple) obj;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public String getVariety() {
        return variety;
    }
    public void setVariety(String variety) {
        this.variety = variety;
    }
    public int getNo() {
        return no;
    }
    public void setNo(int no) {
        this.no = no;
    }
}

  測試下,看結果是不是很簡單,只需要實現Cloneable介面呼叫clone方法就OK

package pattern.prototype.demo;
    
public class test {
    public static void main(String[] args) {
        Apple a= new Apple();
        a.setVariety("富士蘋果");
        System.out.println("原型物件:"+a);
        Apple b = a.cloneApple();
        System.out.println("克隆出來的新物件:"+b);
        System.out.println("兩個物件是否相等:"+(a == b));
        b.setVariety("紅星蘋果");
        System.out.println("兩個物件的蘋果品種:"+b.getVariety()+"、"+a.getVariety());
    }
}

  結果輸出

  4.2、深克隆和淺克隆案例

  程式碼中案例我已組裝電動車為例,要組裝電動車必須有這幾個元件,電車架子、蓄電池、手剎、鋼絲,有了這些元件我們來捋一下他們的關係,組裝電車需要把蓄電池和手剎裝在電車上,組裝手剎需要用到鋼絲繩,他們是一個包含關係即:電車架子>蓄電池>手剎>鋼絲,請看下面程式碼。

  (1),先定義克隆介面

package pattern.prototype;
/**
 * 定義原型模式介面
 * @author ningbeibei
 *
 */
public interface ProtoType {
    //淺克隆
    ProtoType getShallowCloneInstance()throws CloneNotSupportedException;
    //深克隆
    ProtoType getDeepCloneInstance(ProtoType protoType);
}

  (2),定義深克隆工具類,通過序列化方式複製物件

package pattern.prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 原型模式工具類
 * @author ningbeibei
 */
public class ProtoTypeUtil {

    /**
     * 通過序列化方式獲取一個深克隆物件
     * 其實就是複製一個全新的物件並且這個物件的引用屬性也會單獨複製出來
     * 與原物件沒有任何關聯
     * @param prototype
     * @return
     */
    public static ProtoType getSerializInstance(ProtoType prototype) {
        try {
            //建立輸出流
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(prototype);
            //建立輸入流
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            ProtoType copy = (ProtoType) ois.readObject();
            bos.flush();
            //關閉輸出流
            bos.close();
            //關閉輸入流
            ois.close();
            return copy;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

  (3),定義電動車骨架類(屬性中有手剎蓄電池元件)

package pattern.prototype;

import java.io.Serializable;

/**
 * 定義電動車類
 * 組裝一個電動車需要手剎蓄電池等部件
 * 要實現深克隆需要允許序列化反序列化操作
 * Cloneable :要想使用clone方法必須實現這個介面,否則會丟擲異常(java.lang.CloneNotSupportedException)
 * Serializable:允許序列化反序列化物件,通過序列化複製一個新的物件(深克隆會用到)
 * ProtoType:自定義介面,介面中提供淺克隆方法和深克隆方法
 * @author ningbeibei
 */
public class Bicycle implements Cloneable,Serializable,ProtoType{
    //電動車品牌
    public String brand;
    //電動車生產編號
    public int no;
    //手剎元件
    public ParkingBrake parking;
    //蓄電池
    public Accumulator accumulator;
    
    /**
     * 淺克隆方法
     */
    @Override
    public ProtoType getShallowCloneInstance() {
        Object obj=null;
        try {
            obj = super.clone();
            return (Bicycle)obj;
        } catch (CloneNotSupportedException e) {
            System.out.println("不支援克隆");
            return null;
        }
    }

    /**
     * 深克隆方法
     * 呼叫工具類ProtoTypeUtil.getSerializInstance方法複製一個新的物件
     */
    @Override
    public ProtoType getDeepCloneInstance(ProtoType protoType) {
        //呼叫工具類中的序列化物件方法複製物件
        return ProtoTypeUtil.getSerializInstance(protoType);
    }
    
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public int getNo() {
        return no;
    }
    public void setNo(int no) {
        this.no = no;
    }
    public ParkingBrake getParking() {
        return parking;
    }
    public void setParking(ParkingBrake parking) {
        this.parking = parking;
    }
    public Accumulator getAccumulator() {
        return accumulator;
    }
    public void setAccumulator(Accumulator accumulator) {
        this.accumulator = accumulator;
    }
}

  (4),定義蓄電池類

package pattern.prototype;

import java.io.Serializable;

/**
 * 電車元件:蓄電池
 * @author ningbeibei
 */
public class Accumulator implements Serializable,ProtoType,Cloneable{
    //蓄電池品牌
    public String brand;
    //出廠編號
    public int no;
    
    /**
     * 淺克隆方法
     */
    @Override
    public ProtoType getShallowCloneInstance() {
        Object obj=null;
        try {
            obj = super.clone();
            return (Bicycle)obj;
        } catch (CloneNotSupportedException e) {
            System.out.println("不支援克隆");
            return null;
        }
    }

    /**
     * 深克隆方法
     * 呼叫工具類ProtoTypeUtil.getSerializInstance方法複製一個新的物件
     */
    @Override
    public ProtoType getDeepCloneInstance(ProtoType protoType) {
        //呼叫工具類中的序列化物件方法複製物件
        return ProtoTypeUtil.getSerializInstance(protoType);
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }
}

  (5),定義手剎類(手剎類屬性中有鋼絲元件)

package pattern.prototype;

import java.io.Serializable;

/**
 * 電動車元件:手剎
 * @author ningbeibei
 */
public class ParkingBrake implements Serializable, ProtoType,Cloneable {
    //手剎品牌
    public String brand;
    //出廠編號
    public int no;
    //鋼絲
    public SteelWire steel;
    
    /**
     * 淺克隆方法
     */
    @Override
    public ProtoType getShallowCloneInstance() {
        Object obj=null;
        try {
            obj = super.clone();
            return (Bicycle)obj;
        } catch (CloneNotSupportedException e) {
            System.out.println("不支援克隆");
            return null;
        }
    }

    /**
     * 深克隆方法
     * 呼叫工具類ProtoTypeUtil.getSerializInstance方法複製一個新的物件
     */
    @Override
    public ProtoType getDeepCloneInstance(ProtoType protoType) {
        //呼叫工具類中的序列化物件方法複製物件
        return ProtoTypeUtil.getSerializInstance(protoType);
    }
    
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public int getNo() {
        return no;
    }
    public void setNo(int no) {
        this.no = no;
    }
    public SteelWire getSteel() {
        return steel;
    }
    public void setSteel(SteelWire steel) {
        this.steel = steel;
    }
}

  (6),定義鋼絲類

package pattern.prototype;

import java.io.Serializable;

/**
 * 手剎元件;鋼絲
 * 電動車手剎需要用到鋼絲拉動剎車片才能減速
 * 所以手剎是由鋼絲構成
 * @author ningbeibei
 */
public class SteelWire implements Serializable,ProtoType,Cloneable{
    //鋼絲品牌
    public String brand;
    //鋼絲最大拉力值
    public int no;
    
    /**
     * 淺克隆方法
     */
    @Override
    public ProtoType getShallowCloneInstance() {
        Object obj=null;
        try {
            obj = super.clone();
            return (Bicycle)obj;
        } catch (CloneNotSupportedException e) {
            System.out.println("不支援克隆");
            return null;
        }
    }

    /**
     * 深克隆方法
     * 呼叫工具類ProtoTypeUtil.getSerializInstance方法複製一個新的物件
     */
    @Override
    public ProtoType getDeepCloneInstance(ProtoType protoType) {
        //呼叫工具類中的序列化物件方法複製物件
        return ProtoTypeUtil.getSerializInstance(protoType);
    }
}

  上面程式碼中都實現三個介面

  Serializable:表明允許這個類被序列化

  ProtoType:自定義介面需要實現淺克隆和深克隆方法

  Cloneable:表明這個類允許被克隆,如果不實現這個介面在克隆時會丟擲異常

  在上面的程式碼中 Bicycle 類中包含了 ParkingBrake 和 Accumulato r類,在 ParkingBrake 類中包含了 SteelWire 類,這樣就能充分體現深克隆和淺克隆區別,現在可以直接去測試類中看下區別。

  (6),測試類

package pattern.prototype;

public class test {

    public static void main(String[] args) {
                                                                            
        Bicycle bicycle = new Bicycle(); //建立電動車原型物件                                                               
        ParkingBrake attachment = new ParkingBrake(); //建立手剎物件 
        Accumulator accumulator = new Accumulator(); //建立蓄電池物件
        bicycle.setAccumulator(accumulator); //將蓄電池組裝到電動車中
        bicycle.setParking(attachment);    //將手剎新增到電動車中         
        SteelWire steel = new SteelWire();     //鋼絲物件
        attachment.setSteel(steel);    //將鋼絲物件組裝到手剎中
        Bicycle proto = (Bicycle)bicycle.getDeepCloneInstance(bicycle); //深克隆,複製一個新的電動車物件
//        Bicycle proto = (Bicycle)bicycle.getShallowCloneInstance();  //淺克隆,
        
        System.out.println("電動車"+(proto==bicycle)); 
        System.out.println("電動車手剎"+(proto.getParking()==bicycle.getParking()));
        System.out.println("手剎鋼絲"+(proto.getParking().getSteel()==bicycle.getParking().getSteel()));
        System.out.println("電動車蓄電池"+(proto.getAccumulator()==bicycle.getAccumulator()));
    }

}

  深克隆執行結果:都是false說明深克隆出來的物件已經和原物件沒有任何聯絡,修改新的物件也不會影響原來的物件,說明內部手剎鋼絲和蓄電池都已經是獨立存在的。

   淺克隆執行結果:執行結果不難發現手剎、鋼絲、蓄電池都是true,這說明覆制的出來物件內部元件引用都指向了原物件,也就是說新物件和老物件內部元件引用都是一個物件,當修改新物件或者原物件時新物件也會修改,這就是淺克隆和深克隆的區別,當然深克隆到底克隆幾層這個問題還需要深入探討。

5、原型模式優點和缺點

  優點:

  1,當建立的物件例項較為複雜的時候,使用原型模式可以簡化物件的建立過程。
  2,擴充套件性好,由於寫原型模式的時候使用了抽象原型類,在客戶端進行程式設計的時候可以將具體的原型類通過配置進行讀取。
  3,可以使用深度克隆來儲存物件的狀態,使用原型模式進行復制。當你需要恢復到某一時刻就直接跳到。比如我們的idea種就有歷史版本。

  缺點:

  1,需要為每一個類配備一個克隆方法,而且該克隆方法位於一個類的裡面,當對已有的類經行改造時需要修改原始碼,違背了開閉原則。

  2,在實現深克隆的時需要編寫較為複雜的程式碼,而且當物件之間存在多重巢狀引用的時候,為了實現深克隆,每一層物件對應的類都必須支援深克隆,實現相對麻煩。

6、原型模式使用場景

  1,建立物件成本比較大,比如初始化要很長時間的,佔用太多CPU的,新物件可以通過複製已有的物件獲得的,如果是相似的物件,則可以對其成員變數稍作修改。
  2,系統要儲存物件狀態的,而物件的狀態改變很小。
  3,需要避免使用分層次的工廠類來建立分層次的物件,並且類的物件就只用一個或很少的組合狀態。

7、總結

  建立型的設計模式,除開建造者模式基本學習完畢。不過是基礎的學習。還沒有正式的運用!在寫程式碼的時候需要取考慮使用這種設計模式與否。學而不用存粹浪費時間。其次,建立型的設計模式是基礎,需要好好理解這些模式才能夠理解其他的結構型以及行為型的設計模式。  

相關文章