­

java設計模式—原型模式

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、總結

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