天天看點

【設計模式】建立型模式—原型模式(Prototype Pattern)(五)前言一.原型模式(用得最少)二.原型模式适用場景二.原型模式角色三.Java中原型模式的實作方式四.總結

文章目錄

  • 前言
  • 一.原型模式(用得最少)
  • 二.原型模式适用場景
  • 二.原型模式角色
    • 1.角色分類
    • 2.标準的原型模式實作方式
      • 2.1.抽象原型角色(Abstract Prototype)
      • 2.2.具體原型角色(Concrete Prototype)
      • 2.3.測試類
  • 三.Java中原型模式的實作方式
    • 1.淺克隆
      • 1.1.代碼實作
      • 1.1.原型模式和直接new對象方式的比較
    • 2.深克隆
      • 2.1.基于淺克隆嵌套
      • 2.2.基于序列化反序列化
  • 四.總結
    • 1.原型模式的優缺點
    • 2.原型模式在開發中的應用場景

前言

建議先看這篇文章【設計模式】軟體設計7大原則以及23種設計模式初識(一)

一.原型模式(用得最少)

我們知道通過new關鍵字建立的對象是非常繁瑣的(類加載判斷,記憶體配置設定,初始化等),當直接建立對象的代價比較大時,我們可以采用原型模式(Prototype Pattern)。

通過克隆已存在的對象,減少重新建立對象的時間開銷

  • 原型模式也稱為

    "克隆模式"

    , 即某個對象為原型克隆出來一個一模一樣的對象,該對象的屬性和原型對象一模一樣。而且對于原型對象沒有任何影響。原型模式的克隆方式有2種:

    淺克隆和深度克隆

    • 這裡我簡單的說明一下什麼是淺克隆和深克隆
      淺克隆和深克隆的概念:
      • 淺克隆:将一個對象複制後,基本資料類型的變量都會重新建立,而引用類型,指向的還是原對象所指向的。
      • 實作Cloneable 接口,重寫clone()方法
      • 深克隆:将一個對象複制後,不論是基本資料類型還有引用類型,都是重新建立的。簡單來說,就是深克隆進行了完全徹底的複制,而淺克隆不徹底。
      • `實作Serializable接口,将對象進行序列化成二進制流,然後再将二進制流進行反序列化成Java對象
      • 淺克隆和深克隆的實作可以看這片文章【Java基礎】對象深克隆和淺克隆的原理及實作

二.原型模式适用場景

  1. 在需要一個類的

    大量對象

    的時候,使用原型模式是最佳選擇,因為原型模式是

    在記憶體中對這個對象進行拷貝

    ,要比直接new這個對象性能要好很多。
  2. 如果一個對象的初始化需要很多

    其他對象的資料準備或其他資源的繁瑣計算

    ,那麼可以使用原型模式,這樣建立新的對象的時候,可以避免其他對象的資料準備和計算,

    直接得到目前對象的副本

  3. 當需要

    保留一個複雜對象的絕大部分資訊,少量字段進行個性化設定的時候

    ,也可以使用原型模式拷貝出現有對象的副本後再進行加工處理。

二.原型模式角色

1.角色分類

  • 抽象原型角色(Abstract Prototype):這是一個抽象角色,通常由一個

    Java接口或java抽象類實作

    ,此抽象角色規定了

    所有的具體原型類所需的接口

    (如果要提供深拷貝,則必須具有實作clone的規定)。
  • 具體原型角色(Concrete Prototype):即被複制的對象,此角色需要

    實作抽象的原型角色所要求的接口

  • 客戶角色(Client):使用原型對象的客戶程式

2.标準的原型模式實作方式

  • 主要思想

    :實作原型接口,基于現有的對象克隆一個新的對象出來,由對象的内部提供克隆的方法,通過clone方法傳回一個對象的副本。

2.1.抽象原型角色(Abstract Prototype)

原型接口

public interface IPrototype {
    IPrototype clone();
}
           

2.2.具體原型角色(Concrete Prototype)

PrototypeImplA實作了接口IPrototype,并且實作了clone方法,傳回了一個新的對象

public class PrototypeImplA implements IPrototype{
    private String name;
    private int age;
    private List<String> phoneList;

    public String getName() {return name;}
    public void setName(String name) { this.name = name; }

    public int getAge() {return age;}
    public void setAge(int age) {this.age = age;}
    
    public List<String> getPhoneList() {return phoneList;}
    public void setPhoneList(List<String> phoneList) {this.phoneList = phoneList; }

	//實作原型接口clone()方法
    @Override
    public IPrototype clone() {
        PrototypeImplA prototypeImplA = new PrototypeImplA();
        prototypeImplA.setAge(this.age);
        prototypeImplA.setName(this.name);
        prototypeImplA.setPhoneList(this.phoneList);
        return prototypeImplA;
    }
}
           

2.3.測試類

public class ProtoTypeTest {
    public static void main(String[] args) throws Exception {
        PrototypeImplA prototypeImplA = new PrototypeImplA();
        prototypeImplA.setAge(18);
        prototypeImplA.setName("張三");

        List<String> phoneList = new ArrayList<>();
        phoneList.add("88888888");
        phoneList.add("77777777");
        prototypeImplA.setPhoneList(phoneList);

        PrototypeImplA cloneProtoTypeA = (PrototypeImplA) prototypeImplA.clone();

        System.out.println(prototypeImplA+"------"+prototypeImplA.getPhoneList().hashCode());
        System.out.println(cloneProtoTypeA+"------"+cloneProtoTypeA.getPhoneList().hashCode());
    }
}
           
【設計模式】建立型模式—原型模式(Prototype Pattern)(五)前言一.原型模式(用得最少)二.原型模式适用場景二.原型模式角色三.Java中原型模式的實作方式四.總結

生成了2個不同的PrototypeImplA,但因為這種克隆方式是

淺克隆

對象中如果有引用對象那麼被克隆後的對象依然會指向原對象

,如果需要複制兩個獨立的對象,則需要使用

深克隆

,後面示例中我們會對比一下2種克隆方式

對象的HashCode相同,說明是指向的是同一個記憶體位址

三.Java中原型模式的實作方式

1.淺克隆

1.1.代碼實作

/**
 * 原型類:被克隆的類型
 * 	 Object有clone()方法,但不能直接使用,需要實作Cloneable,重寫裡面的clone()方法才能使用
 */

public class Student implements Cloneable {
    public Student() {
        System.out.println("執行了構造方法Student()");
    }

    /**
     * 傳回類型是Object
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //調用Object的clone()
        return super.clone();
    }
}
           

測試

public static void main(String[] args) {
        Student student = new Student();
        try {
            Student cloneStudent= (Student) student.clone();  //通過克隆來建立執行個體

            System.out.println(student);
            System.out.println(cloneStudent);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
           

測試結果

【設計模式】建立型模式—原型模式(Prototype Pattern)(五)前言一.原型模式(用得最少)二.原型模式适用場景二.原型模式角色三.Java中原型模式的實作方式四.總結

1.1.原型模式和直接new對象方式的比較

測試通過原型模式建立100萬對象 和通過new建立100萬對象的性能

public static void main(String[] args) throws CloneNotSupportedException {
        int size = 1_000_000;
        long start1 = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            Student t = new Student();
        }
        long end1 = System.currentTimeMillis();


        long start2 = System.currentTimeMillis();
        Student t = new Student();
        for (int i = 0; i < size; i++) {
            Student temp = (Student) t.clone();
        }
        long end2 = System.currentTimeMillis();


        System.out.println("new的方式建立耗時:" + (end1 - start1));
        System.out.println("clone的方式建立耗時:" + (end2 - start2));
    }
           

執行結果:

【設計模式】建立型模式—原型模式(Prototype Pattern)(五)前言一.原型模式(用得最少)二.原型模式适用場景二.原型模式角色三.Java中原型模式的實作方式四.總結

結論:

  • 通過clone的方式在擷取大量對象的時候性能開銷基本沒有什麼影響,而new的方式随着執行個體的對象越來越多,性能會急劇下降,是以原型模式是一種比較重要的擷取執行個體的方式。

優點:

  • 使用clone方法建立的新對象的

    構造函數是不會被執行的

    ,也就是說會繞過任何構造函數(有參和無參),因為clone方法的原理是從堆記憶體中以二進制流的方式進行拷貝,直接配置設定一塊新記憶體。
  • 一句話:

    clone()直接複制二進制流,不調用構造器建立執行個體

缺點:

  • 被拷貝對象的所有基本類型變量都含有與原對象相同的值,而且

    引用類型的變量仍然是指向原對象位址

    。即

    淺拷貝隻拷貝目前基本類型屬性值,對引用的屬性值不做拷貝

2.深克隆

2.1.基于淺克隆嵌套

public class Student implements Cloneable {
    //引用型成員變量。注意:Teacher類也要實作Cloneable接口,并重寫clone()方法
    private Teacher teacher;

    public Student() {
        System.out.println("執行了構造方法Student()");
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();  //克隆整個對象
        student.teacher = (Teacher) teacher.clone();
        return student;
    }
}
class Teacher implements Cloneable {
    public Teacher() {
        System.out.println("執行了構造方法Teacher()");
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //克隆整個對象
        Teacher teacher = (Teacher) super.clone();
        return teacher;
    }
}

           

測試

public static void main(String[] args) {
        Student student = new Student();
        student.teacher= new Teacher();

        try {
            Student cloneStudent= (Student) student.clone();  //通過克隆來建立執行個體

            System.out.println(student+"------"+student.teacher);
            System.out.println(cloneStudent+"------"+cloneStudent.teacher);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
           

執行結果

【設計模式】建立型模式—原型模式(Prototype Pattern)(五)前言一.原型模式(用得最少)二.原型模式适用場景二.原型模式角色三.Java中原型模式的實作方式四.總結

2.2.基于序列化反序列化

  • 序列化 把對象轉換為位元組流。
  • 反序列化 把位元組流恢複為對象。
/**
 * 序列化實作深克隆
 */
public class Student implements Cloneable, Serializable {
    //引用型成員變量。注意:Teacher類也要實作Serializable
    private Teacher teacher;

    public Student() {
        System.out.println("執行了構造方法Student()");
    }


    public static void main(String[] args) throws  CloneNotSupportedException {
        //原對象
        Student student = new Student();
        student.teacher = new Teacher();

        //克隆對象
        Student cloneStudent = (Student) student.clone();
        System.out.println(student+"-----"+student.teacher);
        System.out.println(cloneStudent+"-----"+cloneStudent.teacher);
    }


    //序列化實作深克隆
    private Object deepClone() {
        try {
            //----------序列化
            //位元組數組輸出流
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            //對象輸出流
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            //将對象寫入對象輸出流
            oos.writeObject(this);
            //輸出成位元組數組
            byte[] bytes = bos.toByteArray();

            //----------反序列化
            //讀取對象位元組數組
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            //讀取位元組數組流
            ObjectInputStream ois = new ObjectInputStream(bis);
            //從位元組流中生成對象
            Student cloneStudent = (Student) ois.readObject();
            return cloneStudent;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return deepClone();
    }
}


class Teacher implements Serializable {
    public Teacher() {
        System.out.println("執行了構造方法Teacher()");
    }
}
           

測試

public static void main(String[] args) throws  CloneNotSupportedException {
        //原對象
        Student student = new Student();
        student.teacher = new Teacher();

        //克隆對象
        Student cloneStudent = (Student) student.clone();
        System.out.println(student+"-----"+student.teacher);
        System.out.println(cloneStudent+"-----"+cloneStudent.teacher);
    }

           
【設計模式】建立型模式—原型模式(Prototype Pattern)(五)前言一.原型模式(用得最少)二.原型模式适用場景二.原型模式角色三.Java中原型模式的實作方式四.總結

四.總結

1.原型模式的優缺點

  • 優點:減少了重新建立執行個體、反複的相同指派操作的性能開銷;
    • 原型模式是在記憶體二進制流的拷貝, 要比直接new一個對象性能好很多, 特别是要在一個循環體内産生大量的對象時, 原型模式可以更
  • 缺點:克隆

    包含循環引用的複雜對象

    會非常麻煩,需要編寫較為複雜的代碼
無論是深克隆還是、淺克隆在沒有經過處理時,都會破壞單例模式,重新生成一個新的執行個體

2.原型模式在開發中的應用場景

  • 原型模式很少單獨出現,

    一般是和工廠方法模式一起出現,通過clone的方法建立一個對象,然後由工廠方法提供給調用者

    • Spring中bean的建立實際就是兩種:單例模式和原型模式。(原型模式需要和工廠模式搭配起來)

原型模式了解到這裡我覺得就夠了,各種變着法子說這種代碼或那種代碼是原型模式,沒什麼意義。

繼續閱讀