常用的設計模式之原型模式

原型模式

  原型模式(Prototype Pattern) 是指原型實例指定創建對象的種類, 並且通過拷貝這些原型創建新

的對象,屬於創建型模式。


官方原文:Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

原型模式的核心在於拷貝原型對象。以系統中已存在的一個對象爲原型,直接基於內存二進制流進

行拷貝,無需再經歷耗時的對象初始化過程(不調用構造函數),性能提升許多。當對象的構建過程比

較耗時時,可以利用當前系統中已存在的對象作爲原型,對其進行克隆(一般是基於二進制流的複製)

通用的類圖如下:

注:對不通過new關鍵字, 而是通過對象拷貝來實現創建對象的模式就稱作原型模式。

原型模式的應用場景:

1、類初始化消耗資源較多。

2new產生的一個對象需要非常繁瑣的過程(數據準備、訪問權限等)

3、構造函數比較複雜。

4、循環體中生產大量對象時。

Spring中, 原型模式應用得非常廣泛。例如scope=”prototype” 在我們經常用的JSON.parse Object(也是一種原型模式。

原型模式的通用寫法

一個標準的原型模式代碼, 應該是這樣設計的。先創建原型I Prototype接口;創建具體需要克隆的對象Concrete Prototype;

如下:

原型接口:

public interface IPrototype<T> {
    T clone();
}

需要克隆的對象:

@Data
public class ConcretePrototype implements IPrototype {

    private int age;
    private String name;


    @Override
    public ConcretePrototype clone() {
        ConcretePrototype concretePrototype = new ConcretePrototype();
        concretePrototype.setAge(this.age);
        concretePrototype.setName(this.name);
        return concretePrototype;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

調用方:

public class Client {


    public static void main(String[] args) {
        //創建原型對象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("Tom");
        System.out.println(prototype);


        //拷貝原型對象
        ConcretePrototype cloneType = prototype.clone();
        System.out.println(cloneType);

    }


}

在這個簡單的場景之下,看上去操作好像變複雜了。但如果有幾百個屬性需要複製,那我們就可以一勞永逸。但是,上面的複製過程是我們自己完成的, 在實際編碼中, 我們一般不會浪費這樣的體力勞動, JDK已經幫我們實現了一個現成的API 我們只需要實現Cloneable接口即可:

如下覆蓋中的方法使用父類clone方法:

  @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

 

下面我們再來做一個測試, Concrete Prototype增加一個個人愛好的屬性hobbies

 

import lombok.Data;

import java.util.List;

@Data
public class ConcretePrototype implements Cloneable {

    private int age;
    private String name;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }


    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", hobbies=" + hobbies +
                '}';
    }
}

再次測試入下:

import java.util.ArrayList;
import java.util.List;
public class Client {


    public static void main(String[] args) {
        //創建原型對象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("dxy");
        List<String> hobbies = new ArrayList<String>();
        hobbies.add("games");
        hobbies.add("reading");
        prototype.setHobbies(hobbies);

        //拷貝原型對象
        ConcretePrototype cloneType = prototype.clone();
        cloneType.getHobbies().add("riding");


        System.out.println("原型對象:" + prototype);
        System.out.println("克隆對象:" + cloneType);
        System.out.println(prototype == cloneType);


        System.out.println("原型對象的愛好:" + prototype.getHobbies());
        System.out.println("克隆對象的愛好:" + cloneType.getHobbies());
        System.out.println(prototype.getHobbies() == cloneType.getHobbies());

    }

}

測試結果:

我們給,複製後的克隆對象新增一項愛好,發現原型對象也發生了變化,這顯然不符合我們的預期。因爲我們希望克隆出來的對象應該和原型對象是兩個獨立的對象,不應該再有聯繫了。從測試結果分析來看, 應該是hobbies共用了一個內存地址, 意味着複製的不是值, 而是引用的地址。這樣的話, 如果我們修改任意一個對象中的屬性值, prototypeclone Type的hobbies值都會改變。這就是我們常說的淺克隆。只是完整複製了值類型數據,沒有賦值引用對象。換言之,所有的引用對象仍然指向原來的對象,顯然不是我們想要的結果。那如何解決這個問題呢?下面我們來看深度克隆繼續改造。

使用序列化實現深度克隆:

@Data
public class ConcretePrototype implements Cloneable,Serializable {

    private int age;
    private String name;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public ConcretePrototype deepCloneHobbies(){
        try {
            ConcretePrototype result = (ConcretePrototype)super.clone();
            result.hobbies = (List)((ArrayList)result.hobbies).clone();
            return result;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public ConcretePrototype deepClone(){
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            return (ConcretePrototype)ois.readObject();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }


    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", hobbies=" + hobbies +
                '}';
    }
}

測試:

public class Client {


    public static void main(String[] args) {
        //創建原型對象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("dxy");
        List<String> hobbies = new ArrayList<String>();
        hobbies.add("music");
        hobbies.add("book");
        prototype.setHobbies(hobbies);

        //拷貝原型對象
        ConcretePrototype cloneType = prototype.deepCloneHobbies();
        cloneType.getHobbies().add("swing");

        System.out.println("原型對象:" + prototype);
        System.out.println("克隆對象:" + cloneType);
        System.out.println(prototype == cloneType);


        System.out.println("原型對象的愛好:" + prototype.getHobbies());
        System.out.println("克隆對象的愛好:" + cloneType.getHobbies());
        System.out.println(prototype.getHobbies() == cloneType.getHobbies());

    }

}

結果:

克隆破壞單例模式

如果我們克隆的目標的對象是單例對象,那意味着,深克隆就會破壞單例。實際上防止克隆破壞單例解決思路非常簡單, 禁止深克隆便可。要麼你我們的單例類不實現Cloneable接口; 要麼我們重寫clone) 方法, clone方法中返回單例對象即可, 具體代碼如下:

import lombok.Data;

import java.util.List;
@Data
public class ConcretePrototype implements Cloneable {

    private int age;
    private String name;
    private List<String> hobbies;

    private static  ConcretePrototype instance = new ConcretePrototype();

    private ConcretePrototype(){}

    public static ConcretePrototype getInstance(){
        return instance;
    }

    @Override
    public ConcretePrototype clone() {
       return instance;
    }


    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", hobbies=" + hobbies +
                '}';
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章