天天看點

Java設計模式之建立型模式--原型模式

一:簡介

原型模式用起來很簡單,我們隻需實作Cloneable接口,重寫clone方法即可。使用原型模式的目的,是為了避免初始化是需要消耗很多的資源,new一個對象時需要進行繁瑣的資料準備。

二:例子

假設,創造一個人,設定他的性别,國籍,種類(黑人還是白人還是黃種人)。等等之類,比方說現在就這三個屬性。

三:例子實作

1. 建立Type:

public class Type {
    //膚色
    private String fuse;
    //頭發顔色
    private String toufa;
    public Type() {
    }
    public Type(String fuse, String toufa) {
        this.fuse = fuse;
        this.toufa = toufa;
    }
    public String getFuse() {
        return fuse;
    }
    public void setFuse(String fuse) {
        this.fuse = fuse;
    }
    public String getToufa() {
        return toufa;
    }
    public void setToufa(String toufa) {
        this.toufa = toufa;
    }
}
           

Type類很簡單。

2.建立People:

這個類需要被我去拷貝,所有需要實作Cloneable接口和重寫clone方法

public class People implements Cloneable {
    //性别
    private String sex;
    //國籍
    private String nationality;
    //種類
    private Type type;
    public People() {
    }
    public People(String sex, String nationality, Type type) {
        this.sex = sex;
        this.nationality = nationality;
        this.type = type;
    }
    //重寫clone方法
    @Override
    protected Object clone(){
        People people = null;
        try {
            //拷貝一個人
            people = (People) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return people;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public String getNationality() {
        return nationality;
    }
    public void setNationality(String nationality) {
        this.nationality = nationality;
    }
    public Type getType() {
        return type;
    }
    public void setType(Type type) {
        this.type = type;
    }
    @Override
    public String toString() {
        return "性别:"+sex+"\n國籍:"+nationality+"\n種類:\t膚色:"+type.getFuse()+"\t頭發:"+type.getToufa();
    }
}
           

3.建立測試類:

public static void main(String[] args) {
        Type type = new Type("黃皮膚","黑頭發");
        People people = new People();
        people.setSex("男");
        people.setNationality("中國");
        people.setType(type);
        System.out.println(people.toString());
        //第一個人建立好了,那如果我們還要建立這樣的一個人呢,
        //我們隻需要clone一下就行了
        People people1 = (People) people.clone();
        System.out.println("---------------------");
        System.out.println(people1.toString());
        //修改一些屬性
        people1.setSex("女");
        System.out.println("---------------------");
        System.out.println(people1.toString());
    }
    結果:
    性别:男
    國籍:中國
    種類: 膚色:黃皮膚  頭發:黑頭發
    ---------------------
    性别:男
    國籍:中國
    種類: 膚色:黃皮膚  頭發:黑頭發
    ---------------------
    性别:女
    國籍:中國
    種類: 膚色:黃皮膚  頭發:黑頭發
           

我們可以看到,我們拷貝了一個一模一樣的人。當然我們也可以動态的修改複制出來的人的一些屬性。這樣我們的對象不再是new出來,不過clone和new都會在記憶體中配置設定位址。并不會減少記憶體的開銷,隻不過,我們有時候用new會有繁瑣的操作,即使實際上隻需改其中的一點點。是以我們用clone就會很好的避免這個問題。

對象拷貝時,構造方法并不會被執行

我們修改預設構造方法:
 public People() {
        System.out.println("構造方法被調用了!");
    }
其他代碼都不變
結果:
構造方法被調用了!
性别:男
國籍:中國
種類: 膚色:黃皮膚  頭發:黑頭發
---------------------
性别:男
國籍:中國
種類: 膚色:黃皮膚  頭發:黑頭發
---------------------
性别:女
國籍:中國
種類: 膚色:黃皮膚  頭發:黑頭發
           

我們可以看到,我們拷貝了兩次,但是構造方法隻是在最開始new的時候被調用了。

這是因為:

Object類的clone方法原理是從記憶體中(堆中)以二進制流的方法進行拷貝,重新配置設定一個記憶體塊。

四:原型中的淺拷貝和深拷貝

1. 先看淺拷貝的例子

還是一個人,裡面有個list

public class People implements Cloneable{
    private ArrayList<String> array = new ArrayList<String>();
    @Override
    protected People clone(){
        People people = null;
        try {
            people = (People) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return people;
    }
    public List<String> getArray() {
        return array;
    }
    public void setValue(String value) {
        this.array.add(value);
    }
}
           

直接測試

public static void main(String[] args) {
        People people = new People();
        people.setValue("張三");
        People p2 = people.clone();
        p2.setValue("李四");
        for (String s : people .getArray())
            System.out.println(s);
    }
    結果:
    張三
    李四
           

我們會發現,people裡應該隻有一個張三啊,怎麼被拷貝一個對象後,那個拷貝對象添加了一個李四,為什麼原先的對象中也被添加了,太奇怪了。

這裡我們要明白兩點:

1.我們的clone方法并不是Cloneable接口的而是Object這個父類的,你可以去點進去看,發現這個接口,并沒有任何方法,這個接口隻是作為一個辨別,隻有實作改接口的類才‘有可能’被拷貝,沒錯是有可能!那麼如何讓這可能變成肯定呢,那就是重寫clone方法。

2.Object提供的clone方法,隻能拷貝一些原始類型如int,long…也包括String。

2.深拷貝例子

我們隻需修改People中clone的代碼就可以了
@Override
    protected People clone(){
        People people = null;
        try {
            people = (People) super.clone();
            //這句話,讓我們的array在對象拷貝的時候也拷貝一下,這裡注意我們不能用List,而是要用它的實作類ArrayList,因為接口并沒有clone方法。
            people.array = (ArrayList<String>)this.array.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return people;
    }
    看看結果:
    張三
           

我們發現這次被拷貝對象并沒有和拷貝對象公用屬性。而是各用個的。這就是深拷貝。

五:适用場景

參考設計模式

一是類初始化需要消化非常多的資源,這個資源包括資料、硬體資源

等;

二是通過 new 産生一個對象需要非常繁瑣的資料準備或通路權限,則可以使用原型模式;

三是一個對象需要提供給其他對象通路,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。

在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone的方法建立一個對象,然後由工廠方法提供給調用者。